diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:46:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:46:43 +0000 |
commit | 3e02d5aff85babc3ffbfcf52313f2108e313aa23 (patch) | |
tree | b01f3923360c20a6a504aff42d45670c58af3ec5 /public/js/icinga/behavior/actiontable.js | |
parent | Initial commit. (diff) | |
download | icingaweb2-3e02d5aff85babc3ffbfcf52313f2108e313aa23.tar.xz icingaweb2-3e02d5aff85babc3ffbfcf52313f2108e313aa23.zip |
Adding upstream version 2.12.1.upstream/2.12.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'public/js/icinga/behavior/actiontable.js')
-rw-r--r-- | public/js/icinga/behavior/actiontable.js | 498 |
1 files changed, 498 insertions, 0 deletions
diff --git a/public/js/icinga/behavior/actiontable.js b/public/js/icinga/behavior/actiontable.js new file mode 100644 index 0000000..0f914f7 --- /dev/null +++ b/public/js/icinga/behavior/actiontable.js @@ -0,0 +1,498 @@ +/* Icinga Web 2 | (c) 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 {Icinga} + * + * @constructor + */ + var Selection = function(table, icinga) { + this.$el = $(table); + this.icinga = icinga; + this.col = this.$el.closest('div.container').attr('id'); + + 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 = { + + /** + * The container id in which this selection happens + */ + col: null, + + /** + * 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[href]'); + }, + + /** + * 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 main 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'); + }, + + /** + * Check whether the given url is + * + * @param {String} url + */ + hasMultiselectionUrl: function(url) { + var urls = this.$el.data('icinga-multiselect-url').split(' '); + + var related = this.$el.data('icinga-multiselect-controllers'); + if (related && related.length) { + urls = urls.concat(this.$el.data('icinga-multiselect-controllers').split(' ')); + } + + var hasSelection = false; + $.each(urls, function (i, object) { + if (url.indexOf(object) === 0) { + hasSelection = true; + } + }); + return hasSelection; + }, + + /** + * 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]; + for (var j = 0; j < params.length; j++) { + if (params[j].key === key && (params[j].value || params[j].value === null)) { + tuple[key] = params[j].value ? decodeURIComponent(params[j].value): params[j].value; + break; + } + } + } + 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 _this = this; + this.rowActions() + .filter( + function (i, el) { + return _this.icinga.utils.objectsEqual(_this.getRowData($(el)), filter); + } + ) + .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) { + var formerHref = this.$el.closest('.container').data('icinga-actiontable-former-href') + + var $row = this.rows().filter('[href="' + url + '"]'); + + if ($row.length) { + this.clear(); + $row.addClass('active'); + } else { + if (this.col !== 'col2') { + // rows sometimes need to be displayed as active when related actions + // like command actions are being opened. Do not do this for col2, as it + // would always select the opened URL itself. + var $row = this.rows().filter('[href$="' + icinga.utils.parseUrl(url).query + '"]'); + if ($row.length) { + this.clear(); + $row.addClass('active'); + } else { + var $row = this.rows().filter('[href$="' + formerHref + '"]'); + if ($row.length) { + this.clear(); + $row.addClass('active'); + } else { + var tbl = this.$el; + if (ActionTable.prototype.tables( + tbl.closest('.dashboard').find('.container')).not(tbl).find('tr.active').length + ) { + this.clear(); + } + } + } + } + } + }, + + /** + * Convert all currently selected rows into an url query string + * + * @returns {String} The filter string + */ + toQuery: function() { + var _this = this; + var selections = this.selections(); + var queries = []; + var utils = this.icinga.utils; + if (selections.length === 1) { + return $(selections[0]).attr('href'); + } else if (selections.length > 1 && _this.hasMultiselection()) { + selections.each(function (i, el) { + var parts = []; + $.each(_this.getRowData($(el)), function(key, value) { + var condition = utils.fixedEncodeURIComponent(key); + if (value !== null) { + condition += '=' + utils.fixedEncodeURIComponent(value); + } + + parts.push(condition); + }); + queries.push('(' + parts.join('&') + ')'); + }); + return _this.getMultiselectionUrl() + '?(' + queries.join('|') + ')'; + } else { + return ''; + } + }, + + /** + * Refresh the displayed active columns using the current page location + */ + refresh: function() { + var hash = icinga.history.getCol2State().replace(/^#!/, ''); + if (this.hasMultiselection()) { + var query = parseSelectionQuery(hash); + if (query.length > 1 && this.hasMultiselectionUrl(this.icinga.utils.parseUrl(hash).path)) { + this.clear(); + // select all rows with matching filters + var _this = this; + $.each(query, function(i, selection) { + _this.select(selection); + }); + } + if (query.length > 1) { + return; + } + } + this.selectUrl(hash); + } + }; + + Icinga.Behaviors = Icinga.Behaviors || {}; + + var ActionTable = function (icinga) { + Icinga.EventListener.call(this, icinga); + + /** + * If currently loading + * + * @var Boolean + */ + this.loading = false; + + this.on('rendered', '#main .container', this.onRendered, this); + this.on('beforerender', '#main .container', this.beforeRender, this); + this.on('click', 'table.action tr[href], table.table-row-selectable 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, table.table-row-selectable'); + } + return $('table.action, table.table-row-selectable'); + }; + + /** + * Handle clicks on table rows and update selection and history + */ + ActionTable.prototype.onRowClicked = function (event) { + var _this = event.data.self; + var $target = $(event.target); + var $tr = $(event.currentTarget); + var table = new Selection($tr.closest('table.action, table.table-row-selectable')[0], _this.icinga); + + if ($tr.closest('[data-no-icinga-ajax]').length > 0) { + return true; + } + + // some rows may contain form actions that trigger a different action, pass those through + if (!$target.hasClass('rowaction') && $target.closest('form').length && + ($target.closest('a').length || // allow regular link clinks + $target.closest('button').length || // allow submitting forms + $target.closest('input').length || $target.closest('label').length)) { // allow selecting form elements + 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); + } + + var count = table.selections().length; + if (count > 0) { + var query = table.toQuery(); + _this.icinga.loader.loadUrl(query, _this.icinga.loader.getLinkTargetFor($tr)); + } else { + if (_this.icinga.loader.getLinkTargetFor($tr).attr('id') === 'col2') { + _this.icinga.ui.layout1col(); + } + } + + // redraw all table selections + _this.tables().each(function () { + new Selection(this, _this.icinga).refresh(); + }); + + // update selection info + $('.selection-info-count').text(count); + return false; + }; + + /** + * Render the selection and prepare selection rows + */ + ActionTable.prototype.onRendered = function(evt) { + var container = evt.target; + var _this = evt.data.self; + + if (evt.currentTarget !== container) { + // Nested containers are not processed multiple times + return; + } + + // initialize all rows with the correct row action + $('table.action tr, table.table-row-selectable tr', container).each(function(idx, el) { + + // decide which row action to use: links declared with the class rowaction take + // the highest precedence before hrefs defined in the tr itself and regular links + var $a = $('a[href].rowaction', el).first(); + if ($a.length) { + $(el).attr('href', $a.attr('href')); + return; + } + if ($(el).attr('href') && $(el).attr('href').length) { + return; + } + $a = $('a[href]', el).first(); + if ($a.length) { + $(el).attr('href', $a.attr('href')); + } + }); + + // draw all active selections that have disappeared on reload + _this.tables().each(function(i, el) { + new Selection(el, _this.icinga).refresh(); + }); + + // update displayed selection counter + var table = new Selection(_this.tables(container).first()); + $(container).find('.selection-info-count').text(table.selections().length); + }; + + ActionTable.prototype.beforeRender = function(evt) { + var container = evt.target; + var _this = evt.data.self; + + if (evt.currentTarget !== container) { + // Nested containers are not processed multiple times + return; + } + + var active = _this.tables().find('tr.active'); + if (active.length) { + $(container).data('icinga-actiontable-former-href', active.attr('href')); + } + }; + + ActionTable.prototype.clearAll = function () { + var _this = this; + this.tables().each(function () { + new Selection(this, _this.icinga).clear(); + }); + $('.selection-info-count').text('0'); + }; + + Icinga.Behaviors.ActionTable = ActionTable; + +}) (Icinga, jQuery); |