icingaweb2/public/js/icinga/behavior/actiontable.js

397 lines
12 KiB
JavaScript
Raw Normal View History

/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
/**
* Icinga.Behavior.ActionTable
*
* A multi selection that distincts between the table rows using the row action URL filter
*/
(function(Icinga, $) {
"use strict";
/**
* Remove one leading and trailing bracket and all text outside those brackets
*
* @param str {String}
* @returns {string}
*/
var stripBrackets = function (str) {
return str.replace(/^[^\(]*\(/, '').replace(/\)[^\)]*$/, '');
};
/**
* Parse the filter query contained in the given url filter string
*
* @param filterString {String}
*
* @returns {Array} An object containing each row filter
*/
var parseSelectionQuery = function(filterString) {
var selections = [];
$.each(stripBrackets(filterString).split('|'), function(i, row) {
var tuple = {};
$.each(stripBrackets(row).split('&'), function(i, keyValue) {
var s = keyValue.split('=');
tuple[s[0]] = decodeURIComponent(s[1]);
});
selections.push(tuple);
});
return selections;
};
/**
* Handle the selection of an action table
*
* @param table {HTMLElement} The table
* @param {Icinga}
*
* @constructor
*/
var Selection = function(table, icinga) {
this.$el = $(table);
this.icinga = icinga;
if (this.hasMultiselection()) {
if (! this.getMultiselectionKeys().length) {
icinga.logger.error('multiselect table has no data-icinga-multiselect-data');
}
if (! this.getMultiselectionUrl()) {
icinga.logger.error('multiselect table has no data-icinga-multiselect-url');
}
}
};
Selection.prototype = {
/**
* Return all rows as jQuery selector
*
* @returns {jQuery}
*/
rows: function() {
return this.$el.find('tr');
},
/**
* Return all row action links as jQuery selector
*
* @returns {jQuery}
*/
rowActions: function() {
return this.$el.find('tr a.rowaction');
},
/**
* Return all selected rows as jQuery selector
*
* @returns {jQuery}
*/
selections: function() {
return this.$el.find('tr.active');
},
/**
* If this selection allows selecting multiple rows
*
* @returns {Boolean}
*/
hasMultiselection: function() {
return this.$el.hasClass('multiselect');
},
/**
* Return all filter keys that are significant when applying the selection
*
* @returns {Array}
*/
getMultiselectionKeys: function() {
var data = this.$el.data('icinga-multiselect-data');
return (data && data.split(',')) || [];
},
/**
* Return the target URL that is used when multi selecting rows
*
* This URL may differ from the url that is used when applying single rows
*
* @returns {String}
*/
getMultiselectionUrl: function() {
return this.$el.data('icinga-multiselect-url');
},
/**
* Read all filter data from the given row
*
* @param row {jQuery} The row element
*
* @returns {Object} An object containing all filter data in this row as key-value pairs
*/
getRowData: function(row) {
var params = this.icinga.utils.parseUrl(row.attr('href')).params;
var tuple = {};
var keys = this.getMultiselectionKeys();
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (params[key]) {
tuple[key] = params[key];
}
}
return tuple;
},
/**
* Deselect all selected rows
*/
clear: function() {
this.selections().removeClass('active');
},
/**
* Add all rows that match the given filter to the selection
*
* @param filter {jQuery|Object} Either an object containing filter variables or the actual row to select
*/
select: function(filter) {
if (filter instanceof jQuery) {
filter.addClass('active');
return;
}
var self = this;
var url = this.getMultiselectionUrl();
this.rowActions()
.filter(
function (i, el) {
var params = self.getRowData($(el));
if (self.icinga.utils.objectKeys(params).length !== self.icinga.utils.objectKeys(filter).length) {
return false;
}
var equal = true;
$.each(params, function(key, value) {
if (filter[key] !== value) {
equal = false;
}
});
return equal;
}
)
.closest('tr')
.addClass('active');
},
/**
* Toggle the selection of the row between on and off
*
* @param row {jQuery} The row to toggle
*/
toggle: function(row) {
row.toggleClass('active');
},
/**
* Add a new selection range to the closest table, using the selected row as
* range target.
*
* @param row {jQuery} The target of the selected range.
*
* @returns {boolean} If the selection was changed.
*/
range: function(row) {
var from, to;
var selected = row.first().get(0);
this.rows().each(function(i, el) {
if ($(el).hasClass('active') || el === selected) {
if (!from) {
from = el;
}
to = el;
}
});
var inRange = false;
this.rows().each(function(i, el) {
if (el === from) {
inRange = true;
}
if (inRange) {
$(el).addClass('active');
}
if (el === to) {
inRange = false;
}
});
return false;
},
/**
* Select rows that target the given url
*
* @param url {String} The target url
*/
selectUrl: function(url) {
this.rows().filter('[href="' + url + '"]').addClass('active');
},
/**
* Convert all currently selected rows into an url query string
*
* @returns {String} The filter string
*/
toQuery: function() {
var self = this;
var selections = this.selections();
var queries = [];
if (selections.length === 1) {
return $(selections[0]).attr('href');
} else if (selections.length > 1 && self.hasMultiselection()) {
selections.each(function (i, el) {
var parts = [];
$.each(self.getRowData($(el)), function(key, value) {
parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
});
queries.push('(' + parts.join('&') + ')');
});
return self.getMultiselectionUrl() + '?(' + queries.join('|') + ')';
} else {
return '';
}
},
/**
* Refresh the displayed active columns using the current page location
*/
refresh: function() {
this.clear();
var hash = this.icinga.utils.parseUrl(window.location.href).hash;
if (this.hasMultiselection()) {
var query = parseSelectionQuery(hash);
if (query.length > 1 && this.getMultiselectionUrl() === this.icinga.utils.parseUrl(hash.substr(1)).path) {
// select all rows with matching filters
var self = this;
$.each(query, function(i, selection) {
self.select(selection);
});
}
if (query.length > 1) {
return;
}
}
this.selectUrl(hash.substr(1));
}
};
Icinga.Behaviors = Icinga.Behaviors || {};
var ActionTable = function (icinga) {
Icinga.EventListener.call(this, icinga);
/**
* The hash that is currently being loaded
*
* @var String
*/
this.loadingHash = null;
/**
* If currently loading
*
* @var Boolean
*/
this.loading = false;
this.on('rendered', this.onRendered, this);
this.on('click', 'table.action tr[href]', this.onRowClicked, this);
};
ActionTable.prototype = new Icinga.EventListener();
/**
* Return all active tables in this table, or in the context as jQuery selector
*
* @param context {HTMLElement}
* @returns {jQuery}
*/
ActionTable.prototype.tables = function(context) {
if (context) {
return $(context).find('table.action');
}
return $('table.action');
};
/**
* Handle clicks on table rows and update selection and history
*/
ActionTable.prototype.onRowClicked = function (event) {
var self = event.data.self;
var $tr = $(event.target).closest('tr');
var table = new Selection($tr.closest('table.action')[0], self.icinga);
// allow form actions in table rows to pass through
if ($(event.target).closest('form').length) {
return;
}
event.stopPropagation();
event.preventDefault();
// update selection
if (table.hasMultiselection()) {
if (event.ctrlKey || event.metaKey) {
// add to selection
table.toggle($tr);
} else if (event.shiftKey) {
// range selection
table.range($tr);
} else {
// single selection
table.clear();
table.select($tr);
}
} else {
table.clear();
table.select($tr);
}
// update history
var url = self.icinga.utils.parseUrl(window.location.href.split('#')[0]);
var count = table.selections().length;
var state = url.path + url.query;
if (count > 0) {
var query = table.toQuery();
self.icinga.loader.loadUrl(query, self.icinga.events.getLinkTargetFor($tr));
state += '#!' + query;
} else {
if (self.icinga.events.getLinkTargetFor($tr).attr('id') === 'col2') {
self.icinga.ui.layout1col();
}
}
self.icinga.history.pushUrl(state);
// re draw all table selections
self.tables().each(function () {
new Selection(this, self.icinga).refresh();
});
// update selection info
$('.selection-info-count').text(table.selections().size());
return false;
};
/**
* Ensure that
*/
ActionTable.prototype.onRendered = function(evt) {
var container = evt.target;
var self = evt.data.self;
// draw all selections
self.tables().each(function(i, el) {
new Selection(el, self.icinga).refresh();
});
// update displayed selection count
var table = new Selection(self.tables(container).first());
$(container).find('.selection-info-count').text(table.selections().size());
};
Icinga.Behaviors.ActionTable = ActionTable;
}) (Icinga, jQuery);