From dad0122f11d813495cdabac47ea2c0321163c72b Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 12 Jun 2020 10:50:46 +0200 Subject: [PATCH 01/10] js: Introduce behavior complete.js --- public/js/icinga/behavior/complete.js | 82 +++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 public/js/icinga/behavior/complete.js diff --git a/public/js/icinga/behavior/complete.js b/public/js/icinga/behavior/complete.js new file mode 100644 index 000000000..5ac99ccbe --- /dev/null +++ b/public/js/icinga/behavior/complete.js @@ -0,0 +1,82 @@ +/* Icinga Web 2 | (c) 2020 Icinga GmbH | GPLv2+ */ + +/** + * Complete - Behavior for forms with auto-completion of terms + */ +(function(Icinga, $) { + + "use strict"; + + Icinga.Behaviors = Icinga.Behaviors || {}; + + /** + * @param icinga + * @constructor + */ + var Complete = function (icinga) { + Icinga.EventListener.call(this, icinga); + + this.on('beforerender', '.container', this.onBeforeRender, this); + this.on('rendered', '.container', this.onRendered, this); + + /** + * Cached completions + * + * Holds values only during the time between `beforerender` and `rendered` + * + * @type {{}} + */ + this.cachedCompletions = {}; + }; + Complete.prototype = new Icinga.EventListener(); + + /** + * @param event + */ + Complete.prototype.onBeforeRender = function (event) { + var _this = event.data.self; + + var $elements = $('input[data-term-completion]', event.currentTarget); + + // Remember current instances + $elements.each(function () { + var $input = $(this), + completion = $input.data('completion'); + if (completion) { + _this.cachedCompletions[_this.icinga.utils.getDomPath($input[0]).join(' ')] = completion; + } + }); + }; + + /** + * @param event + */ + Complete.prototype.onRendered = function (event) { + var _this = event.data.self; + + // Apply remembered instances + $.each(_this.cachedCompletions, function (inputPath) { + var $input = $(inputPath); + if ($input.length) { + this.refresh($input[0]); + } else { + this.destroy(); + } + + delete _this.cachedCompletions[inputPath]; + }); + + var $elements = $('input[data-term-completion]', event.currentTarget); + + // Create new instances + $elements.each(function() { + var $input = $(this); + if (! $input.data('completion')) { + (new Completion(_this.icinga, this)).bind().restoreTerms(); + } + }); + }; + + Icinga.Behaviors.Complete = Complete; + +})(Icinga, jQuery); From cb24eafdb5306eb3c4f5b62963bd3826fcbb7633 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 17 Jun 2020 09:59:07 +0200 Subject: [PATCH 02/10] JavaScript: Load behavior `complete.js` --- library/Icinga/Web/JavaScript.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/Icinga/Web/JavaScript.php b/library/Icinga/Web/JavaScript.php index fc081c285..96a617914 100644 --- a/library/Icinga/Web/JavaScript.php +++ b/library/Icinga/Web/JavaScript.php @@ -40,7 +40,8 @@ class JavaScript 'js/icinga/behavior/expandable.js', 'js/icinga/behavior/filtereditor.js', 'js/icinga/behavior/selectable.js', - 'js/icinga/behavior/modal.js' + 'js/icinga/behavior/modal.js', + 'js/icinga/behavior/complete.js' ]; protected static $vendorFiles = [ From 5924d610a1e374ca2682051a43e89f40567d3a4c Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 2 Jul 2020 13:48:05 +0200 Subject: [PATCH 03/10] js: Provide additional parameters for events `beforerender` and `rendered` beforerender: content, action, autorefresh, scripted rendered: autorefresh, scripted --- public/js/icinga/loader.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/public/js/icinga/loader.js b/public/js/icinga/loader.js index d49d81ff3..c74fd0d4b 100644 --- a/public/js/icinga/loader.js +++ b/public/js/icinga/loader.js @@ -960,9 +960,9 @@ _this.icinga.loadModule(moduleName); } - $(this).trigger('rendered'); + $(this).trigger('rendered', [req.autorefresh, req.scripted]); }); - req.$target.trigger('rendered'); + req.$target.trigger('rendered', [req.autorefresh, req.scripted]); this.icinga.ui.refreshDebug(); }, @@ -1158,7 +1158,7 @@ } } - $container.trigger('beforerender'); + $container.trigger('beforerender', [content, action, autorefresh, scripted]); var discard = false; $.each(_this.icinga.behaviors, function(name, behavior) { From d19a55311b76dcac7de0c3a376631e881b3e9746 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 2 Jul 2020 13:56:22 +0200 Subject: [PATCH 04/10] complete.js: Keep used terms in case of an autorefresh --- public/js/icinga/behavior/complete.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/public/js/icinga/behavior/complete.js b/public/js/icinga/behavior/complete.js index 5ac99ccbe..16d874c18 100644 --- a/public/js/icinga/behavior/complete.js +++ b/public/js/icinga/behavior/complete.js @@ -32,8 +32,12 @@ /** * @param event + * @param content + * @param action + * @param autorefresh + * @param scripted */ - Complete.prototype.onBeforeRender = function (event) { + Complete.prototype.onBeforeRender = function (event, content, action, autorefresh, scripted) { var _this = event.data.self; var $elements = $('input[data-term-completion]', event.currentTarget); @@ -43,6 +47,10 @@ var $input = $(this), completion = $input.data('completion'); if (completion) { + if (! completion.keepUsedTerms) { + completion.keepUsedTerms = autorefresh; + } + _this.cachedCompletions[_this.icinga.utils.getDomPath($input[0]).join(' ')] = completion; } }); @@ -50,8 +58,10 @@ /** * @param event + * @param autorefresh + * @param scripted */ - Complete.prototype.onRendered = function (event) { + Complete.prototype.onRendered = function (event, autorefresh, scripted) { var _this = event.data.self; // Apply remembered instances From 3c41c14653271a89d14d1ed919562e88670a5171 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 27 Jul 2020 15:04:31 +0200 Subject: [PATCH 05/10] complete.js: Properly manage enrichment persistence --- public/js/icinga/behavior/complete.js | 80 ++++++++++++++++----------- 1 file changed, 47 insertions(+), 33 deletions(-) diff --git a/public/js/icinga/behavior/complete.js b/public/js/icinga/behavior/complete.js index 16d874c18..2b2646b67 100644 --- a/public/js/icinga/behavior/complete.js +++ b/public/js/icinga/behavior/complete.js @@ -3,7 +3,7 @@ /** * Complete - Behavior for forms with auto-completion of terms */ -(function(Icinga, $) { +(function(Icinga) { "use strict"; @@ -13,20 +13,29 @@ * @param icinga * @constructor */ - var Complete = function (icinga) { + let Complete = function (icinga) { Icinga.EventListener.call(this, icinga); this.on('beforerender', '.container', this.onBeforeRender, this); this.on('rendered', '.container', this.onRendered, this); /** - * Cached completions + * Enriched inputs + * + * @type {WeakMap} + * @private + */ + this._enrichments = new WeakMap(); + + /** + * Cached enrichments * * Holds values only during the time between `beforerender` and `rendered` * * @type {{}} + * @private */ - this.cachedCompletions = {}; + this._cachedEnrichments = {}; }; Complete.prototype = new Icinga.EventListener(); @@ -38,20 +47,18 @@ * @param scripted */ Complete.prototype.onBeforeRender = function (event, content, action, autorefresh, scripted) { - var _this = event.data.self; + if (! autorefresh) { + return; + } - var $elements = $('input[data-term-completion]', event.currentTarget); + let _this = event.data.self; + let inputs = event.currentTarget.querySelectorAll('input[data-term-completion]'); // Remember current instances - $elements.each(function () { - var $input = $(this), - completion = $input.data('completion'); - if (completion) { - if (! completion.keepUsedTerms) { - completion.keepUsedTerms = autorefresh; - } - - _this.cachedCompletions[_this.icinga.utils.getDomPath($input[0]).join(' ')] = completion; + inputs.forEach((input) => { + let enrichment = _this._enrichments.get(input); + if (enrichment) { + _this._cachedEnrichments[_this.icinga.utils.getDomPath(input).join(' ')] = enrichment; } }); }; @@ -62,31 +69,38 @@ * @param scripted */ Complete.prototype.onRendered = function (event, autorefresh, scripted) { - var _this = event.data.self; + let _this = event.data.self; + let container = event.currentTarget; - // Apply remembered instances - $.each(_this.cachedCompletions, function (inputPath) { - var $input = $(inputPath); - if ($input.length) { - this.refresh($input[0]); - } else { - this.destroy(); + if (autorefresh) { + // Apply remembered instances + for (let inputPath in _this._cachedEnrichments) { + let enrichment = _this._cachedEnrichments[inputPath]; + let input = container.querySelector(inputPath); + if (input !== null) { + enrichment.refresh(input); + _this._enrichments.set(input, enrichment); + } else { + enrichment.destroy(); + } + + delete _this._cachedEnrichments[inputPath]; } - - delete _this.cachedCompletions[inputPath]; - }); - - var $elements = $('input[data-term-completion]', event.currentTarget); + } // Create new instances - $elements.each(function() { - var $input = $(this); - if (! $input.data('completion')) { - (new Completion(_this.icinga, this)).bind().restoreTerms(); + let inputs = container.querySelectorAll('input[data-term-completion]'); + inputs.forEach((input) => { + let enrichment = _this._enrichments.get(input); + if (! enrichment) { + enrichment = (new FilterInput(input)).bind(); + enrichment.restoreTerms(); + + _this._enrichments.set(input, enrichment); } }); }; Icinga.Behaviors.Complete = Complete; -})(Icinga, jQuery); +})(Icinga); From 4019522da169ed3265c809814f6b699a86a2f377 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 28 Jul 2020 10:11:39 +0200 Subject: [PATCH 06/10] loader.js: Disable inputs in forms not having role `search` Previously only the `#search` input wasn't disabled, now also the new filter input isn't. This is required to re-focus the input after submission as disabled elements loose focus. --- public/js/icinga/loader.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/js/icinga/loader.js b/public/js/icinga/loader.js index c74fd0d4b..68d62e0e9 100644 --- a/public/js/icinga/loader.js +++ b/public/js/icinga/loader.js @@ -129,8 +129,8 @@ // Disable all form controls to prevent resubmission except for our search input // Note that disabled form inputs will not be enabled via JavaScript again - if ($target.attr('id') === $form.closest('.container').attr('id')) { - $form.find(':input:not(#search):not(:disabled)').prop('disabled', true); + if (! $form.is('[role="search"]') && $target.attr('id') === $form.closest('.container').attr('id')) { + $form.find('input:not(:disabled)').prop('disabled', true); } // Show a spinner depending on how the form is being submitted From fcd0a0a698776e3df87becda5f0cb36353c40477 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 29 Jul 2020 09:54:49 +0200 Subject: [PATCH 07/10] complete.js: Keep IE11 compatibility --- public/js/icinga/behavior/complete.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/public/js/icinga/behavior/complete.js b/public/js/icinga/behavior/complete.js index 2b2646b67..c4e6b5949 100644 --- a/public/js/icinga/behavior/complete.js +++ b/public/js/icinga/behavior/complete.js @@ -13,7 +13,7 @@ * @param icinga * @constructor */ - let Complete = function (icinga) { + var Complete = function (icinga) { Icinga.EventListener.call(this, icinga); this.on('beforerender', '.container', this.onBeforeRender, this); @@ -55,8 +55,8 @@ let inputs = event.currentTarget.querySelectorAll('input[data-term-completion]'); // Remember current instances - inputs.forEach((input) => { - let enrichment = _this._enrichments.get(input); + inputs.forEach(function (input) { + var enrichment = _this._enrichments.get(input); if (enrichment) { _this._cachedEnrichments[_this.icinga.utils.getDomPath(input).join(' ')] = enrichment; } @@ -74,9 +74,9 @@ if (autorefresh) { // Apply remembered instances - for (let inputPath in _this._cachedEnrichments) { - let enrichment = _this._cachedEnrichments[inputPath]; - let input = container.querySelector(inputPath); + for (var inputPath in _this._cachedEnrichments) { + var enrichment = _this._cachedEnrichments[inputPath]; + var input = container.querySelector(inputPath); if (input !== null) { enrichment.refresh(input); _this._enrichments.set(input, enrichment); @@ -89,9 +89,9 @@ } // Create new instances - let inputs = container.querySelectorAll('input[data-term-completion]'); - inputs.forEach((input) => { - let enrichment = _this._enrichments.get(input); + var inputs = container.querySelectorAll('input[data-term-completion]'); + inputs.forEach(function (input) { + var enrichment = _this._enrichments.get(input); if (! enrichment) { enrichment = (new FilterInput(input)).bind(); enrichment.restoreTerms(); From 67de8a079a5dca99c1278f663c1a6eebe7dff847 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 30 Sep 2020 15:47:51 +0200 Subject: [PATCH 08/10] js: Initialize all types of input enrichments --- library/Icinga/Web/JavaScript.php | 2 +- .../{complete.js => input-enrichment.js} | 32 ++++++++++++------- 2 files changed, 22 insertions(+), 12 deletions(-) rename public/js/icinga/behavior/{complete.js => input-enrichment.js} (67%) diff --git a/library/Icinga/Web/JavaScript.php b/library/Icinga/Web/JavaScript.php index 96a617914..f0ad3c543 100644 --- a/library/Icinga/Web/JavaScript.php +++ b/library/Icinga/Web/JavaScript.php @@ -41,7 +41,7 @@ class JavaScript 'js/icinga/behavior/filtereditor.js', 'js/icinga/behavior/selectable.js', 'js/icinga/behavior/modal.js', - 'js/icinga/behavior/complete.js' + 'js/icinga/behavior/input-enrichment.js' ]; protected static $vendorFiles = [ diff --git a/public/js/icinga/behavior/complete.js b/public/js/icinga/behavior/input-enrichment.js similarity index 67% rename from public/js/icinga/behavior/complete.js rename to public/js/icinga/behavior/input-enrichment.js index c4e6b5949..a8818dd14 100644 --- a/public/js/icinga/behavior/complete.js +++ b/public/js/icinga/behavior/input-enrichment.js @@ -1,7 +1,7 @@ /* Icinga Web 2 | (c) 2020 Icinga GmbH | GPLv2+ */ /** - * Complete - Behavior for forms with auto-completion of terms + * InputEnrichment - Behavior for forms with enriched inputs */ (function(Icinga) { @@ -13,7 +13,7 @@ * @param icinga * @constructor */ - var Complete = function (icinga) { + var InputEnrichment = function (icinga) { Icinga.EventListener.call(this, icinga); this.on('beforerender', '.container', this.onBeforeRender, this); @@ -22,7 +22,7 @@ /** * Enriched inputs * - * @type {WeakMap} + * @type {WeakMap} * @private */ this._enrichments = new WeakMap(); @@ -37,7 +37,7 @@ */ this._cachedEnrichments = {}; }; - Complete.prototype = new Icinga.EventListener(); + InputEnrichment.prototype = new Icinga.EventListener(); /** * @param event @@ -46,13 +46,13 @@ * @param autorefresh * @param scripted */ - Complete.prototype.onBeforeRender = function (event, content, action, autorefresh, scripted) { + InputEnrichment.prototype.onBeforeRender = function (event, content, action, autorefresh, scripted) { if (! autorefresh) { return; } let _this = event.data.self; - let inputs = event.currentTarget.querySelectorAll('input[data-term-completion]'); + let inputs = event.currentTarget.querySelectorAll('input[data-enrichment-type]'); // Remember current instances inputs.forEach(function (input) { @@ -68,7 +68,7 @@ * @param autorefresh * @param scripted */ - Complete.prototype.onRendered = function (event, autorefresh, scripted) { + InputEnrichment.prototype.onRendered = function (event, autorefresh, scripted) { let _this = event.data.self; let container = event.currentTarget; @@ -89,18 +89,28 @@ } // Create new instances - var inputs = container.querySelectorAll('input[data-term-completion]'); + var inputs = container.querySelectorAll('input[data-enrichment-type]'); inputs.forEach(function (input) { var enrichment = _this._enrichments.get(input); if (! enrichment) { - enrichment = (new FilterInput(input)).bind(); - enrichment.restoreTerms(); + switch (input.dataset.enrichmentType) { + case 'filter': + enrichment = (new FilterInput(input)).bind(); + enrichment.restoreTerms(); + break; + case 'terms': + enrichment = (new TermInput(input)).bind(); + enrichment.restoreTerms(); + break; + case 'completion': + enrichment = (new Completer(input)).bind(); + } _this._enrichments.set(input, enrichment); } }); }; - Icinga.Behaviors.Complete = Complete; + Icinga.Behaviors.InputEnrichment = InputEnrichment; })(Icinga); From dc5e39ef1c1cf94d44a1a5b803e8ee83d5f90422 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 6 Nov 2020 11:35:38 +0100 Subject: [PATCH 09/10] input-enrichment.js: Add method `update()` --- public/js/icinga/behavior/input-enrichment.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/public/js/icinga/behavior/input-enrichment.js b/public/js/icinga/behavior/input-enrichment.js index a8818dd14..452a8d924 100644 --- a/public/js/icinga/behavior/input-enrichment.js +++ b/public/js/icinga/behavior/input-enrichment.js @@ -39,6 +39,16 @@ }; InputEnrichment.prototype = new Icinga.EventListener(); + /** + * @param data + */ + InputEnrichment.prototype.update = function (data) { + var input = document.querySelector(data[0]); + if (input !== null && this._enrichments.has(input)) { + this._enrichments.get(input).updateTerms(data[1]); + } + }; + /** * @param event * @param content From eff5c4e1d1812d06d241ef119a0f5b3cf2cc034f Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 12 Nov 2020 15:43:31 +0100 Subject: [PATCH 10/10] input-enrichment.js: Import required classes with `require()` --- public/js/icinga/behavior/input-enrichment.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/public/js/icinga/behavior/input-enrichment.js b/public/js/icinga/behavior/input-enrichment.js index 452a8d924..e75b167ed 100644 --- a/public/js/icinga/behavior/input-enrichment.js +++ b/public/js/icinga/behavior/input-enrichment.js @@ -7,6 +7,10 @@ "use strict"; + var FilterInput = require('icinga/ipl/widget/FilterInput'); + var TermInput = require('icinga/ipl/widget/TermInput'); + var Completer = require('icinga/ipl/widget/Completer'); + Icinga.Behaviors = Icinga.Behaviors || {}; /**