From 7f78c1a8a80c5e95b4ec3f1e7de536e2eb208c8e Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 20 Nov 2019 16:11:01 +0100 Subject: [PATCH] js: Properly initialize modules and don't trigger `rendered` events twice Cleans up how our JS initializes module javascript. Previously the `rendered` event only got fired upon page load if a module got loaded as well. This is now decoupled and `rendered` events fire for all containers and the entire layout upon page load. Notable changes: * A `load` event initializes modules and triggers `rendered` on `.container` and `#layout` elements * Module javascript (module.js) is still lazy loaded but with a `beforerender` event, not `rendered` * Previously `#layout` received a `rendered` event during bootstrapping, this is not the case anymore * Initial search value preservation now got its own handler `events.onRenderedMenu()` * Initial dashlet loading now git its own handler `events.loadDashlets()` --- public/js/icinga/events.js | 155 ++++++++++++++++++------------------- 1 file changed, 77 insertions(+), 78 deletions(-) diff --git a/public/js/icinga/events.js b/public/js/icinga/events.js index b1a8027bb..d326b30df 100644 --- a/public/js/icinga/events.js +++ b/public/js/icinga/events.js @@ -14,7 +14,6 @@ this.searchValue = ''; this.searchTimer = null; - this.initializeModules = true; }; Icinga.Events.prototype = { @@ -24,87 +23,16 @@ */ initialize: function () { this.applyGlobalDefaults(); - $('#layout').trigger('rendered'); - //$('.container').trigger('rendered'); $('.container').each(function(idx, el) { icinga.ui.initializeControls($(el)); }); }, - // TODO: What's this? - applyHandlers: function (event) { - var $target = $(event.target); - var _this = event.data.self; - var icinga = _this.icinga; - - if (! icinga) { - // Attempt to catch a rare error, race condition, whatever - console.log('Got no icinga in applyHandlers'); - return; - } - - if (_this.initializeModules) { - var loaded = false; - var moduleName = $target.data('icingaModule'); - if (moduleName) { - if (icinga.hasModule(moduleName) && !icinga.isLoadedModule(moduleName)) { - loaded |= icinga.loadModule(moduleName); - } - } - - $('.icinga-module', $target).each(function(idx, mod) { - moduleName = $(mod).data('icingaModule'); - if (icinga.hasModule(moduleName) && !icinga.isLoadedModule(moduleName)) { - loaded |= icinga.loadModule(moduleName); - } - }); - - if (loaded) { - // Modules may register their own handler for the 'renderend' event - // so we need to ensure that it is called the first time they are - // initialized - event.stopImmediatePropagation(); - _this.initializeModules = false; - - var $container = $target.closest('.container'); - if (! $container.length) { - // The page obviously got loaded for the first time, - // so we'll trigger the event for all containers - $container = $('.container'); - } - - $container.trigger('rendered'); - - // But since we're listening on this event by ourself, we'll have - // to abort our own processing as we'll process it twice otherwise - return false; - } - } else { - _this.initializeModules = true; - } - - $('.dashboard > div', $target).each(function(idx, el) { - var $element = $(el); - var $url = $element.data('icingaUrl'); - if (typeof $url !== 'undefined') { - icinga.loader.loadUrl($url, $element).autorefresh = true; - } - }); - - var $searchField = $('#menu input.search', $target); - // Remember initial search field value if any - if ($searchField.length && $searchField.val().length) { - _this.searchValue = $searchField.val(); - } - }, - /** * Global default event handlers */ applyGlobalDefaults: function () { - // Apply element-specific behavior whenever the layout is rendered - // Note: It is important that this is the first handler for this event! - $(document).on('rendered', { self: this }, this.applyHandlers); + $(document).on('beforerender', { self: this }, this.initializeModules); $(document).on('visibilitychange', { self: this }, this.onVisibilityChange); @@ -141,18 +69,52 @@ $(document).on('focus', 'form select[data-related-radiobtn]', { self: this }, this.autoCheckRadioButton); $(document).on('focus', 'form input[data-related-radiobtn]', { self: this }, this.autoCheckRadioButton); - $(document).on('keyup', '#menu input.search', {self: this}, this.autoSubmitSearch); + $(document).on('rendered', '#menu', { self: this }, this.onRenderedMenu); + $(document).on('keyup', '#search', { self: this }, this.autoSubmitSearch); $(document).on('click', '.tree .handle', { self: this }, this.treeNodeToggle); $(document).on('click', '#search + .search-reset', this.clearSearch); + $(document).on('rendered', '.container', { self: this }, this.loadDashlets); + // TBD: a global autocompletion handler // $(document).on('keyup', 'form.auto input', this.formChangeDelayed); // $(document).on('change', 'form.auto input', this.formChanged); // $(document).on('change', 'form.auto select', this.submitForm); }, + /** + * Lazy load module javascript (Applies only to module.js code) + * + * @param {Event} event + */ + initializeModules: function (event) { + var _this, $target; + + if (typeof event === 'undefined') { + _this = this; + $target = $('#col1'); + } else { + _this = event.data.self; + $target = $(event.target); + } + + var moduleName = $target.data('icingaModule'); + if (moduleName) { + if (_this.icinga.hasModule(moduleName) && ! _this.icinga.isLoadedModule(moduleName)) { + _this.icinga.loadModule(moduleName); + } + } + + $target.find('.icinga-module').each(function () { + moduleName = $(this).data('icingaModule'); + if (_this.icinga.hasModule(moduleName) && !_this.icinga.isLoadedModule(moduleName)) { + _this.icinga.loadModule(moduleName); + } + }); + }, + treeNodeToggle: function () { var $parent = $(this).closest('li'); if ($parent.hasClass('collapsed')) { @@ -167,7 +129,16 @@ onLoad: function (event) { $('body').removeClass('loading'); - //$('.container').trigger('rendered'); + + // First initialize already included modules + event.data.self.initializeModules(); + + // Then trigger the initial `rendered` events + $('.container').trigger('rendered'); + + // Additionally trigger a `rendered` event on the layout, some behaviors may + // want to differentiate whether a container or the entire layout is rendered + $('#layout').trigger('rendered'); }, onUnload: function (event) { @@ -197,12 +168,25 @@ return true; }, + onRenderedMenu: function(event) { + var _this = event.data.self; + var $target = $(event.target); + + var $searchField = $target.find('input.search'); + // Remember initial search field value if any + if ($searchField.length && $searchField.val().length) { + _this.searchValue = $searchField.val(); + } + }, + autoSubmitSearch: function(event) { var _this = event.data.self; - if ($('#menu input.search').val() === _this.searchValue) { + var $searchField = $(event.target); + + if ($searchField.val() === _this.searchValue) { return; } - _this.searchValue = $('#menu input.search').val(); + _this.searchValue = $searchField.val(); if (_this.searchTimer !== null) { clearTimeout(_this.searchTimer); @@ -210,11 +194,26 @@ } var _event = $.extend({}, event); // event seems gc'd once the timeout is over _this.searchTimer = setTimeout(function () { - _this.submitForm(_event, true); + _this.submitForm(_event, $searchField); _this.searchTimer = null; }, 500); }, + loadDashlets: function(event) { + var _this = event.data.self; + var $target = $(event.target); + + if ($target.children('.dashboard').length) { + $target.find('.dashboard > .container').each(function () { + var $dashlet = $(this); + var url = $dashlet.data('icingaUrl'); + if (typeof url !== 'undefined') { + _this.icinga.loader.loadUrl(url, $dashlet).autorefresh = true; + } + }); + } + }, + rememberSubmitButton: function(e) { var $button = $(this); var $form = $button.closest('form');