From c1c35369d1ae017971e6d8cbafc06844f02fd00d Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Mon, 14 Jun 2021 14:44:03 +0200 Subject: [PATCH 01/81] feat: implemented an event manager --- .../src/lib/event-manager/event-manager.js | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 frontend/src/lib/event-manager/event-manager.js diff --git a/frontend/src/lib/event-manager/event-manager.js b/frontend/src/lib/event-manager/event-manager.js new file mode 100644 index 000000000..810cfab55 --- /dev/null +++ b/frontend/src/lib/event-manager/event-manager.js @@ -0,0 +1,65 @@ + +export const EVENT_TYPE = { + CLICK : 'click', + KEYDOWN : 'keydown', + //more to be added +}; + + + +export class EventManager { + _registeredListeners; + + + constructor() { + this._registeredListeners = []; + } + + registerNewListener(eventWrapper) { + this._debugLog('registerNewListener', eventWrapper); + let element = eventWrapper.element; + element.addEventListener(eventWrapper.eventType, eventWrapper.eventHandler); + this._registeredListeners.push(eventWrapper); + } + + removeAllEventListenersFromUtil() { + this._debugLog('removeAllEventListenersFromUtil',); + for (let eventWrapper of this._registeredListeners) { + let element = eventWrapper.element; + element.removeEventListener(eventWrapper.eventType, eventWrapper.eventHandler); + } + this._registeredListeners = []; + } + + //_debugLog() {} + _debugLog(fName, ...args) { + console.log(`[DEBUGLOG] EventManager.${fName}`, { args: args, instance: this }); + } +} + +export class EventWrapper { + _eventType; + _eventHandler; + _element; + + constructor(_eventType, _eventHandler, _element) { + if(!_eventType || !_eventHandler || !_element) { + throw new Error('Not enough arguments!'); + } + this._eventType = _eventType; + this._eventHandler = _eventHandler; + this._element = _element; + } + + get eventType() { + return this._eventType; + } + + get eventHandler() { + return this._eventHandler; + } + + get element() { + return this._element; + } +} \ No newline at end of file From d1b995269060c670d85f715671bfc9947b9f3e9a Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Mon, 14 Jun 2021 14:53:07 +0200 Subject: [PATCH 02/81] fix(enter-is-tab.js): implemented destroy method in enter-is-tab Util --- frontend/src/utils/form/enter-is-tab.js | 44 +++++++++++++++---------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/frontend/src/utils/form/enter-is-tab.js b/frontend/src/utils/form/enter-is-tab.js index 4e171c87b..0a3073b9e 100644 --- a/frontend/src/utils/form/enter-is-tab.js +++ b/frontend/src/utils/form/enter-is-tab.js @@ -1,4 +1,5 @@ import { Utility } from '../../core/utility'; +import { EventManager, EventWrapper, EVENT_TYPE } from '../../lib/event-manager/event-manager'; const ENTER_IS_TAB_INITIALIZED_CLASS = 'enter-as-tab--initialized'; const AREA_SELECTOR = 'input, textarea'; @@ -10,6 +11,8 @@ const AREA_SELECTOR = 'input, textarea'; export class EnterIsTab { _element; + _eventManager; + constructor(element) { if(!element) { @@ -17,6 +20,8 @@ export class EnterIsTab { } this._element = element; + + this._eventManager = new EventManager(); if (this._element.classList.contains(ENTER_IS_TAB_INITIALIZED_CLASS)) { return false; @@ -27,27 +32,32 @@ export class EnterIsTab { start() { - this._element.addEventListener('keydown', (e) => { - if(e.key === 'Enter') { - e.preventDefault(); - let currentInputFieldId = this._element.id; - let inputAreas = document.querySelectorAll(AREA_SELECTOR); - let nextInputArea = null; - for (let i = 0; i < inputAreas.length; i++) { - if(inputAreas[i].id === currentInputFieldId) { - nextInputArea = inputAreas[i+1]; - break; - } - } - - if(nextInputArea) { - nextInputArea.focus(); + let eventWrapper = new EventWrapper(EVENT_TYPE.KEYDOWN, this._captureEnter.bind(this), this._element); + this._eventManager.registerNewListener(eventWrapper); + } + + _captureEnter (e) { + if(e.key === 'Enter') { + e.preventDefault(); + let currentInputFieldId = this._element.id; + let inputAreas = document.querySelectorAll(AREA_SELECTOR); + let nextInputArea = null; + for (let i = 0; i < inputAreas.length; i++) { + if(inputAreas[i].id === currentInputFieldId) { + nextInputArea = inputAreas[i+1]; + break; } } - }); + + if(nextInputArea) { + nextInputArea.focus(); + } + } } destroy() { - console.log('TBD: Destroy EnterIsTab'); + this._eventManager.removeAllEventListenersFromUtil(); + if(this._element.classList.contains(ENTER_IS_TAB_INITIALIZED_CLASS)) + this._element.classList.remove(ENTER_IS_TAB_INITIALIZED_CLASS); } } \ No newline at end of file From f1ef2e5ec776ad8a1fb7737eb0ed4f79218afd61 Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Fri, 18 Jun 2021 13:51:02 +0200 Subject: [PATCH 03/81] feat(util_registry): impelmented destroyAll(scope) method in the utilRegistry --- .../src/lib/event-manager/event-manager.js | 2 + .../services/util-registry/util-registry.js | 45 ++++++++++++++----- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/frontend/src/lib/event-manager/event-manager.js b/frontend/src/lib/event-manager/event-manager.js index 810cfab55..e8121131f 100644 --- a/frontend/src/lib/event-manager/event-manager.js +++ b/frontend/src/lib/event-manager/event-manager.js @@ -31,6 +31,8 @@ export class EventManager { this._registeredListeners = []; } + + //Todo: Uncomment debug log! //_debugLog() {} _debugLog(fName, ...args) { console.log(`[DEBUGLOG] EventManager.${fName}`, { args: args, instance: this }); diff --git a/frontend/src/services/util-registry/util-registry.js b/frontend/src/services/util-registry/util-registry.js index 65e9326cc..dd4a38af6 100644 --- a/frontend/src/services/util-registry/util-registry.js +++ b/frontend/src/services/util-registry/util-registry.js @@ -4,8 +4,8 @@ const DEBUG_MODE = /localhost/.test(window.location.href) ? 1 : 0; export class UtilRegistry { - _registeredUtils = new Array(); - _activeUtilInstances = new Array(); + _registeredUtilClasses = new Array(); //{utilClass} + _activeUtilInstancesWrapped = new Array(); //{utilClass, scope, element, instance} _appInstance; /** @@ -33,7 +33,7 @@ export class UtilRegistry { console.log('registering util "' + util.name + '"'); console.log({ util }); } - this._registeredUtils.push(util); + this._registeredUtilClasses.push(util); } deregister(name, destroy) { @@ -44,7 +44,7 @@ export class UtilRegistry { this._destroyUtilInstances(name); } - this._registeredUtils.splice(utilIndex, 1); + this._registeredUtilClasses.splice(utilIndex, 1); } } @@ -54,7 +54,7 @@ export class UtilRegistry { initAll(scope = document.body) { let startedInstances = new Array(); - const setupInstances = this._registeredUtils.map((util) => this.setup(util, scope)).flat(); + const setupInstances = this._registeredUtilClasses.map((util) => this.setup(util, scope)).flat(); const orderedInstances = setupInstances.filter(_isStartOrdered); @@ -97,6 +97,17 @@ export class UtilRegistry { return startedInstances; } + destroyAll(scope = document.body) { + let utilsInScope = this._getUtilInstancesWithinScope(scope); + + utilsInScope.forEach((util) => { + //if(DEBUG_MODE > 2) { + console.log('Destroying Util: ', {util}); + //} + util.destroy(); + }); + } + setup(util, scope = document.body) { if (DEBUG_MODE > 2) { console.log('setting up util', { util }); @@ -130,12 +141,12 @@ export class UtilRegistry { }); } - this._activeUtilInstances.push(...instances); + this._activeUtilInstancesWrapped.push(...instances); return instances; } find(name) { - return this._registeredUtils.find((util) => util.name === name); + return this._registeredUtilClasses.find((util) => util.name === name); } _findUtilElements(util, scope) { @@ -146,11 +157,23 @@ export class UtilRegistry { } _findUtilIndex(name) { - return this._registeredUtils.findIndex((util) => util.name === name); + return this._registeredUtilClasses.findIndex((util) => util.name === name); + } + + _getUtilInstancesWithinScope(scope) { + let utilInstances = []; + + for (let activeUtilInstance of this._activeUtilInstancesWrapped) { + let util = activeUtilInstance.util; + if(this._findUtilElements(util, scope).length > 0) { + utilInstances.push(activeUtilInstance.instance); + } + } + return utilInstances; } _destroyUtilInstances(name) { - this._activeUtilInstances + this._activeUtilInstancesWrapped .map((util, index) => ({ util: util, index: index, @@ -159,11 +182,11 @@ export class UtilRegistry { .forEach((activeUtil) => { // destroy util instance activeUtil.util.destroy(); - delete this._activeUtilInstances[activeUtil.index]; + delete this._activeUtilInstancesWrapped[activeUtil.index]; }); // get rid of now empty array slots - this._activeUtilInstances = this._activeUtilInstances.filter((util) => !!util); + this._activeUtilInstancesWrapped = this._activeUtilInstancesWrapped.filter((util) => !!util); } } From 01c239d6c3c29b68e191c1f5e866391ab5020b07 Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Mon, 21 Jun 2021 12:23:08 +0200 Subject: [PATCH 04/81] chore(util_registry_test): added test case to deregister method when instances should be destroyed --- .../services/util-registry/util-registry.js | 9 +-- .../util-registry/util-registry.spec.js | 65 ++++++++++++++----- 2 files changed, 50 insertions(+), 24 deletions(-) diff --git a/frontend/src/services/util-registry/util-registry.js b/frontend/src/services/util-registry/util-registry.js index dd4a38af6..b342d812a 100644 --- a/frontend/src/services/util-registry/util-registry.js +++ b/frontend/src/services/util-registry/util-registry.js @@ -178,15 +178,12 @@ export class UtilRegistry { util: util, index: index, })) - .filter((activeUtil) => activeUtil.util.name === name) + .filter((activeUtil) => activeUtil.util.util.name === name) .forEach((activeUtil) => { // destroy util instance - activeUtil.util.destroy(); - delete this._activeUtilInstancesWrapped[activeUtil.index]; + activeUtil.util.instance.destroy(); + this._activeUtilInstancesWrapped = this._activeUtilInstancesWrapped.splice(activeUtil.index, 1); }); - - // get rid of now empty array slots - this._activeUtilInstancesWrapped = this._activeUtilInstancesWrapped.filter((util) => !!util); } } diff --git a/frontend/src/services/util-registry/util-registry.spec.js b/frontend/src/services/util-registry/util-registry.spec.js index 07b9e2627..00f8806b3 100644 --- a/frontend/src/services/util-registry/util-registry.spec.js +++ b/frontend/src/services/util-registry/util-registry.spec.js @@ -24,24 +24,6 @@ describe('UtilRegistry', () => { }); }); - describe('deregister()', () => { - it('should remove util', () => { - // register util - utilRegistry.register(TestUtil1); - let foundUtil = utilRegistry.find(TestUtil1.name); - expect(foundUtil).toBeTruthy(); - - // deregister util - utilRegistry.deregister(TestUtil1.name); - foundUtil = utilRegistry.find(TestUtil1.name); - expect(foundUtil).toBeFalsy(); - }); - - it('should destroy util instances if requested', () => { - pending('TBD'); - }); - }); - describe('setup()', () => { it('should catch errors thrown by the utility', () => { @@ -107,6 +89,51 @@ describe('UtilRegistry', () => { }); }); + describe('deregister()', () => { + let testScope; + let testElement1; + let testElement2; + + beforeEach(() => { + testScope = document.createElement('div'); + testElement1 = document.createElement('div'); + testElement2 = document.createElement('div'); + testElement1.classList.add('util1'); + testElement2.classList.add('util1'); + testScope.appendChild(testElement1); + testScope.appendChild(testElement2); + }); + + it('should remove util', () => { + // register util + utilRegistry.register(TestUtil1); + let foundUtil = utilRegistry.find(TestUtil1.name); + expect(foundUtil).toBeTruthy(); + + // deregister util + utilRegistry.deregister(TestUtil1.name); + foundUtil = utilRegistry.find(TestUtil1.name); + expect(foundUtil).toBeFalsy(); + }); + + it('should destroy util instances if requested', () => { + utilRegistry.register(TestUtil1); + let foundUtil = utilRegistry.find(TestUtil1.name); + expect(foundUtil).toBeTruthy(); + + utilRegistry.setup(TestUtil1, testScope); + let firstActiveUtil = utilRegistry._activeUtilInstancesWrapped[0]; + expect(utilRegistry._activeUtilInstancesWrapped.length).toEqual(2); + expect(utilRegistry._activeUtilInstancesWrapped[0].element).toEqual(testElement1); + + spyOn(firstActiveUtil.instance, 'destroy'); + + utilRegistry.deregister(TestUtil1.name, true); + expect(utilRegistry._activeUtilInstancesWrapped[0]).toBeFalsy(); + expect(firstActiveUtil.instance.destroy).toHaveBeenCalled(); + }); + }); + describe('initAll()', () => { it('should setup all the utilities', () => { spyOn(utilRegistry, 'setup'); @@ -181,6 +208,8 @@ class TestUtil1 { this.element = element; this.app = app; } + + destroy() {} } @Utility({ selector: '#util2' }) From ded4c1f64e22c0d3e6cd4d10aea518d044656480 Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Mon, 21 Jun 2021 16:03:36 +0200 Subject: [PATCH 05/81] chore(util-registry.spec): added a test for destroyAll Method --- .../services/util-registry/util-registry.js | 4 +- .../util-registry/util-registry.spec.js | 39 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/frontend/src/services/util-registry/util-registry.js b/frontend/src/services/util-registry/util-registry.js index b342d812a..b8dea6ec3 100644 --- a/frontend/src/services/util-registry/util-registry.js +++ b/frontend/src/services/util-registry/util-registry.js @@ -103,8 +103,10 @@ export class UtilRegistry { utilsInScope.forEach((util) => { //if(DEBUG_MODE > 2) { console.log('Destroying Util: ', {util}); - //} + //}# + let utilIndex = this._activeUtilInstancesWrapped.indexOf(util); util.destroy(); + this._activeUtilInstancesWrapped.splice(utilIndex, 1); }); } diff --git a/frontend/src/services/util-registry/util-registry.spec.js b/frontend/src/services/util-registry/util-registry.spec.js index 00f8806b3..18506be99 100644 --- a/frontend/src/services/util-registry/util-registry.spec.js +++ b/frontend/src/services/util-registry/util-registry.spec.js @@ -199,6 +199,44 @@ describe('UtilRegistry', () => { }); }); }); + + describe('destroyAll()', () => { + let testScope; + let testElement; + let firstUtil; + + beforeEach( () => { + testScope = document.createElement('div'); + testElement = document.createElement('div'); + testElement.classList.add('util3'); + testScope.appendChild(testElement); + + utilRegistry.register(TestUtil3); + utilRegistry.initAll(testScope); + + firstUtil = utilRegistry._activeUtilInstancesWrapped[0]; + spyOn(firstUtil.instance, 'destroy'); + }); + + it('Util should be destroyed', () => { + utilRegistry.destroyAll(testScope); + expect(utilRegistry._activeUtilInstancesWrapped.length).toBe(0); + expect(firstUtil.instance.destroy).toHaveBeenCalled(); + }); + + it('Util out of scope should not be destroyed', () => { + let outOfScope = document.createElement('div'); + expect(utilRegistry._activeUtilInstancesWrapped.length).toEqual(1); + + utilRegistry.destroyAll(outOfScope); + + expect(utilRegistry._activeUtilInstancesWrapped.length).toEqual(1); + expect(utilRegistry._activeUtilInstancesWrapped[0]).toBe(firstUtil); + expect(firstUtil.instance.destroy).not.toHaveBeenCalled(); + + }); + }); + }); // test utilities @@ -219,6 +257,7 @@ class TestUtil2 { } class TestUtil3 { constructor() {} start() {} + destroy() {} } @Utility({ selector: '#throws' }) From b2c3f77966dd16d282938fb907c5dcc29b1f84a3 Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Fri, 25 Jun 2021 11:41:44 +0200 Subject: [PATCH 06/81] chore(form-error-reporter): implemented destroy in form-error-reporter --- .../src/lib/event-manager/event-manager.js | 2 ++ .../src/utils/form/form-error-reporter.js | 33 +++++++++++++++---- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/frontend/src/lib/event-manager/event-manager.js b/frontend/src/lib/event-manager/event-manager.js index e8121131f..e133ce018 100644 --- a/frontend/src/lib/event-manager/event-manager.js +++ b/frontend/src/lib/event-manager/event-manager.js @@ -2,6 +2,8 @@ export const EVENT_TYPE = { CLICK : 'click', KEYDOWN : 'keydown', + INVALID : 'invalid', + CHANGE : 'change', //more to be added }; diff --git a/frontend/src/utils/form/form-error-reporter.js b/frontend/src/utils/form/form-error-reporter.js index dff3f00d2..8ebd14d5f 100644 --- a/frontend/src/utils/form/form-error-reporter.js +++ b/frontend/src/utils/form/form-error-reporter.js @@ -1,5 +1,6 @@ import { Utility } from '../../core/utility'; import * as defer from 'lodash.defer'; +import { EventManager, EventWrapper, EVENT_TYPE } from '../../lib/event-manager/event-manager'; const FORM_ERROR_REPORTER_INITIALIZED_CLASS = 'form-error-remover--initialized'; @@ -10,12 +11,16 @@ export class FormErrorReporter { _element; _err; + _eventManager; + constructor(element) { if (!element) throw new Error('Form Error Reporter utility needs to be passed an element!'); this._element = element; + this._eventManager = new EventManager(); + if (this._element.classList.contains(FORM_ERROR_REPORTER_INITIALIZED_CLASS)) return; @@ -24,11 +29,23 @@ export class FormErrorReporter { start() { if (this._element.willValidate) { - this._element.addEventListener('invalid', this.report.bind(this)); - this._element.addEventListener('change', () => { defer(this.report.bind(this)); } ); + let invalidElementEvent = new EventWrapper(EVENT_TYPE.INVALID, this.report.bind(this), this._element); + this._eventManager.registerNewListener(invalidElementEvent); + + let changedElementEvent = new EventWrapper(EVENT_TYPE.CHANGE, () => { defer(this.report.bind(this)); }, this._element); + this._eventManager.registerNewListener(changedElementEvent); } } + destroy() { + this._eventManager.removeAllEventListenersFromUtil(); + + this._removeError(); + + if(this._element.classList.contains(FORM_ERROR_REPORTER_INITIALIZED_CLASS)) + this._element.classList.remove(FORM_ERROR_REPORTER_INITIALIZED_CLASS); + } + report() { const msg = this._element.validity.valid ? null : this._element.validationMessage; @@ -37,10 +54,7 @@ export class FormErrorReporter { if (!target) return; - if (this._err && this._err.parentNode) { - this._err.parentNode.removeChild(this._err); - this._err = undefined; - } + this._removeError(); if (!msg) { target.classList.remove('standalone-field--has-error', 'form-group--has-error'); @@ -65,4 +79,11 @@ export class FormErrorReporter { } } } + + _removeError() { + if (this._err && this._err.parentNode) { + this._err.parentNode.removeChild(this._err); + this._err = undefined; + } + } } From 4812db6c8756a52e7f9db26b8e620c0f38a4546f Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Fri, 25 Jun 2021 12:19:13 +0200 Subject: [PATCH 07/81] chore(alerts): implemented destroy method in alerts.js --- frontend/src/utils/alerts/alerts.js | 29 +++++++++++++++++++----- frontend/src/utils/alerts/alerts.spec.js | 8 ++++++- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/frontend/src/utils/alerts/alerts.js b/frontend/src/utils/alerts/alerts.js index dcecc915b..4f84d8aac 100644 --- a/frontend/src/utils/alerts/alerts.js +++ b/frontend/src/utils/alerts/alerts.js @@ -1,4 +1,5 @@ import { Utility } from '../../core/utility'; +import { EventManager, EventWrapper, EVENT_TYPE } from '../../lib/event-manager/event-manager'; import './alerts.sass'; const ALERTS_INITIALIZED_CLASS = 'alerts--initialized'; @@ -32,6 +33,8 @@ export class Alerts { _element; _app; + _eventManager; + constructor(element, app) { if (!element) { throw new Error('Alerts util has to be called with an element!'); @@ -40,6 +43,8 @@ export class Alerts { this._element = element; this._app = app; + this._eventManager = new EventManager(); + if (this._element.classList.contains(ALERTS_INITIALIZED_CLASS)) { return false; } @@ -48,6 +53,7 @@ export class Alerts { this._alertElements = this._gatherAlertElements(); if (this._togglerElement) { + //should there be a start method, to initialize the listeners in initToggler and initAlerts or is this wanted? this._initToggler(); } @@ -61,7 +67,14 @@ export class Alerts { } destroy() { - console.log('TBD: Destroy Alert'); + this._eventManager.removeAllEventListenersFromUtil(); + + if(this._alertElements) { + this._alertElements.forEach(element => element.remove() ); + } + + if(this._element.classList.contains(ALERTS_INITIALIZED_CLASS)) + this._element.classList.remove(ALERTS_INITIALIZED_CLASS); } _gatherAlertElements() { @@ -71,10 +84,12 @@ export class Alerts { } _initToggler() { - this._togglerElement.addEventListener('click', () => { + let clickListenerToggler = new EventWrapper(EVENT_TYPE.CLICK, () => { this._alertElements.forEach((alertEl) => this._toggleAlert(alertEl, true)); this._togglerElement.classList.remove(ALERTS_TOGGLER_VISIBLE_CLASS); - }); + }, this._togglerElement); + + this._eventManager.registerNewListener(clickListenerToggler); } _initAlerts() { @@ -88,9 +103,11 @@ export class Alerts { } const closeEl = alertElement.querySelector('.' + ALERT_CLOSER_CLASS); - closeEl.addEventListener('click', () => { - this._toggleAlert(alertElement); - }); + const closeAlertEvent = new EventWrapper(EVENT_TYPE.CLICK, () => { + this._toggleAlert(alertElement).bind(this); + }, closeEl); + + this._eventManager.registerNewListener(closeAlertEvent); if (autoHideDelay > 0 && alertElement.matches(ALERT_AUTOCLOSING_MATCHER)) { window.setTimeout(() => this._toggleAlert(alertElement), autoHideDelay * 1000); diff --git a/frontend/src/utils/alerts/alerts.spec.js b/frontend/src/utils/alerts/alerts.spec.js index 0b4749e97..889229f7c 100644 --- a/frontend/src/utils/alerts/alerts.spec.js +++ b/frontend/src/utils/alerts/alerts.spec.js @@ -1,4 +1,4 @@ -import { Alerts } from './alerts'; +import { Alerts, ALERTS_INITIALIZED_CLASS } from './alerts'; const MOCK_APP = { httpClient: { @@ -19,6 +19,12 @@ describe('Alerts', () => { expect(alerts).toBeTruthy(); }); + it('should destory alerts', () => { + alerts.destroy(); + expect(alerts._eventManager._registeredListeners.length).toBe(0); + expect(alerts._element.classList).not.toContain(ALERTS_INITIALIZED_CLASS); + }); + it('should throw if called without an element', () => { expect(() => { new Alerts(); From a25b59b3184c8b8e77b05c7defbe7301565dd60b Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Fri, 25 Jun 2021 13:20:21 +0200 Subject: [PATCH 08/81] chore(asidenav): implemented destroy in asidenav.js --- .../src/lib/event-manager/event-manager.js | 11 ++++++++-- frontend/src/utils/asidenav/asidenav.js | 22 ++++++++++++++----- frontend/src/utils/asidenav/asidenav.spec.js | 8 ++++++- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/frontend/src/lib/event-manager/event-manager.js b/frontend/src/lib/event-manager/event-manager.js index e133ce018..a73a30fc2 100644 --- a/frontend/src/lib/event-manager/event-manager.js +++ b/frontend/src/lib/event-manager/event-manager.js @@ -4,6 +4,7 @@ export const EVENT_TYPE = { KEYDOWN : 'keydown', INVALID : 'invalid', CHANGE : 'change', + MOUSE_OVER : 'mouseover', //more to be added }; @@ -20,7 +21,7 @@ export class EventManager { registerNewListener(eventWrapper) { this._debugLog('registerNewListener', eventWrapper); let element = eventWrapper.element; - element.addEventListener(eventWrapper.eventType, eventWrapper.eventHandler); + element.addEventListener(eventWrapper.eventType, eventWrapper.eventHandler, eventWrapper.options); this._registeredListeners.push(eventWrapper); } @@ -45,14 +46,16 @@ export class EventWrapper { _eventType; _eventHandler; _element; + _options - constructor(_eventType, _eventHandler, _element) { + constructor(_eventType, _eventHandler, _element, _options) { if(!_eventType || !_eventHandler || !_element) { throw new Error('Not enough arguments!'); } this._eventType = _eventType; this._eventHandler = _eventHandler; this._element = _element; + this._options = _options; } get eventType() { @@ -66,4 +69,8 @@ export class EventWrapper { get element() { return this._element; } + + get options() { + return this._options; + } } \ No newline at end of file diff --git a/frontend/src/utils/asidenav/asidenav.js b/frontend/src/utils/asidenav/asidenav.js index 6bf425ffa..560183e8c 100644 --- a/frontend/src/utils/asidenav/asidenav.js +++ b/frontend/src/utils/asidenav/asidenav.js @@ -1,4 +1,5 @@ import { Utility } from '../../core/utility'; +import { EventManager, EventWrapper, EVENT_TYPE } from '../../lib/event-manager/event-manager'; import './asidenav.sass'; const FAVORITES_BTN_CLASS = 'navbar__list-item--favorite'; @@ -15,6 +16,7 @@ export class Asidenav { _element; _asidenavSubmenus; + _eventManager; constructor(element) { if (!element) { @@ -23,6 +25,8 @@ export class Asidenav { this._element = element; + this._eventManager = new EventManager(); + if (this._element.classList.contains(ASIDENAV_INITIALIZED_CLASS)) { return false; } @@ -35,19 +39,24 @@ export class Asidenav { } destroy() { - this._asidenavSubmenus.forEach((union) => { - union.listItem.removeEventListener(union.hoverHandler); - }); + + this._eventManager.removeAllEventListenersFromUtil(); + + if(this._element.classList.contains(ASIDENAV_INITIALIZED_CLASS)) + this._element.classList.remove(ASIDENAV_INITIALIZED_CLASS); } _initFavoritesButton() { const favoritesBtn = document.querySelector('.' + FAVORITES_BTN_CLASS); if (favoritesBtn) { - favoritesBtn.addEventListener('click', (event) => { + + const favoritesButtonEvent = new EventWrapper(EVENT_TYPE.CLICK, (event) => { favoritesBtn.classList.toggle(FAVORITES_BTN_ACTIVE_CLASS); this._element.classList.toggle(ASIDENAV_EXPANDED_CLASS); event.preventDefault(); - }, true); + }, favoritesBtn, true); + + this._eventManager.registerNewListener(favoritesButtonEvent); } } @@ -62,7 +71,8 @@ export class Asidenav { this._asidenavSubmenus.forEach((union) => { union.hoverHandler = this._createMouseoverHandler(union); - union.listItem.addEventListener('mouseover', union.hoverHandler); + let currentHoverEvent = new EventWrapper(EVENT_TYPE.MOUSE_OVER, union.hoverHandler, union.listItem); + this._eventManager.registerNewListener(currentHoverEvent); }); } diff --git a/frontend/src/utils/asidenav/asidenav.spec.js b/frontend/src/utils/asidenav/asidenav.spec.js index bdc7aee68..3bd468b28 100644 --- a/frontend/src/utils/asidenav/asidenav.spec.js +++ b/frontend/src/utils/asidenav/asidenav.spec.js @@ -1,4 +1,4 @@ -import { Asidenav } from './asidenav'; +import { Asidenav, ASIDENAV_INITIALIZED_CLASS } from './asidenav'; describe('Asidenav', () => { @@ -13,6 +13,12 @@ describe('Asidenav', () => { expect(asidenav).toBeTruthy(); }); + it('should destory asidenav', () => { + asidenav.destroy(); + expect(asidenav._eventManager._registeredListeners.length).toBe(0); + expect(asidenav._element.classList).not.toContain(ASIDENAV_INITIALIZED_CLASS); + }); + it('should throw if called without an element', () => { expect(() => { new Asidenav(); From e2aa913ff377261cd6f014dd362a4e9888ec7b32 Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Mon, 28 Jun 2021 09:33:53 +0200 Subject: [PATCH 09/81] chore(async-form): implemented destroy method in async-form --- frontend/src/lib/event-manager/event-manager.js | 1 + frontend/src/utils/async-form/async-form.js | 13 +++++++++++-- frontend/src/utils/async-form/async-form.spec.js | 8 +++++++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/frontend/src/lib/event-manager/event-manager.js b/frontend/src/lib/event-manager/event-manager.js index a73a30fc2..5b590052f 100644 --- a/frontend/src/lib/event-manager/event-manager.js +++ b/frontend/src/lib/event-manager/event-manager.js @@ -5,6 +5,7 @@ export const EVENT_TYPE = { INVALID : 'invalid', CHANGE : 'change', MOUSE_OVER : 'mouseover', + SUBMIT : 'submit', //more to be added }; diff --git a/frontend/src/utils/async-form/async-form.js b/frontend/src/utils/async-form/async-form.js index 6c0da95c0..19e147be8 100644 --- a/frontend/src/utils/async-form/async-form.js +++ b/frontend/src/utils/async-form/async-form.js @@ -1,5 +1,6 @@ import { Utility } from '../../core/utility'; import { Datepicker } from '../form/datepicker'; +import { EventManager, EventWrapper, EVENT_TYPE } from '../../lib/event-manager/event-manager'; import './async-form.sass'; const ASYNC_FORM_INITIALIZED_CLASS = 'check-all--initialized'; @@ -20,6 +21,8 @@ export class AsyncForm { _element; _app; + _eventManager; + constructor(element, app) { if (!element) { throw new Error('Async Form Utility cannot be setup without an element!'); @@ -28,17 +31,23 @@ export class AsyncForm { this._element = element; this._app = app; + this._eventManager = new EventManager(); + if (this._element.classList.contains(ASYNC_FORM_INITIALIZED_CLASS)) { return false; } - this._element.addEventListener('submit', this._submitHandler); + const submitEvent = new EventWrapper(EVENT_TYPE.SUBMIT, this._submitHandler.bind(this), this._element); + this._eventManager.registerNewListener(submitEvent); this._element.classList.add(ASYNC_FORM_INITIALIZED_CLASS); } destroy() { - // TODO + this._eventManager.removeAllEventListenersFromUtil(); + + if(this._element.classList.contains(ASYNC_FORM_INITIALIZED_CLASS)) + this._element.classList.remove(ASYNC_FORM_INITIALIZED_CLASS); } _processResponse(response) { diff --git a/frontend/src/utils/async-form/async-form.spec.js b/frontend/src/utils/async-form/async-form.spec.js index f01280b8a..aeb7ce4ba 100644 --- a/frontend/src/utils/async-form/async-form.spec.js +++ b/frontend/src/utils/async-form/async-form.spec.js @@ -1,4 +1,4 @@ -import { AsyncForm } from './async-form'; +import { AsyncForm, ASYNC_FORM_INITIALIZED_CLASS } from './async-form'; describe('AsyncForm', () => { @@ -13,6 +13,12 @@ describe('AsyncForm', () => { expect(asyncForm).toBeTruthy(); }); + it('should destroy asyncForm', () => { + asyncForm.destroy(); + expect(asyncForm._eventManager._registeredListeners.length).toBe(0); + expect(asyncForm._element.classList).not.toContain(ASYNC_FORM_INITIALIZED_CLASS); + }); + it('should throw if called without an element', () => { expect(() => { new AsyncForm(); From 29da3d795fead500dba34b0ce0ca92e94d6548ca Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Mon, 28 Jun 2021 11:40:10 +0200 Subject: [PATCH 10/81] chore(async-table): implemented destroy method in async table --- .../src/lib/event-manager/event-manager.js | 1 + frontend/src/utils/async-table/async-table.js | 57 +++++++++++++------ .../src/utils/async-table/async-table.spec.js | 9 ++- 3 files changed, 49 insertions(+), 18 deletions(-) diff --git a/frontend/src/lib/event-manager/event-manager.js b/frontend/src/lib/event-manager/event-manager.js index 5b590052f..a1400486a 100644 --- a/frontend/src/lib/event-manager/event-manager.js +++ b/frontend/src/lib/event-manager/event-manager.js @@ -6,6 +6,7 @@ export const EVENT_TYPE = { CHANGE : 'change', MOUSE_OVER : 'mouseover', SUBMIT : 'submit', + INPUT : 'input', //more to be added }; diff --git a/frontend/src/utils/async-table/async-table.js b/frontend/src/utils/async-table/async-table.js index 93d3bcf99..0e3c49685 100644 --- a/frontend/src/utils/async-table/async-table.js +++ b/frontend/src/utils/async-table/async-table.js @@ -2,6 +2,7 @@ import { Utility } from '../../core/utility'; import { StorageManager, LOCATION } from '../../lib/storage-manager/storage-manager'; import { Datepicker } from '../form/datepicker'; import { HttpClient } from '../../services/http-client/http-client'; +import { EventManager, EventWrapper, EVENT_TYPE } from '../../lib/event-manager/event-manager'; import * as debounce from 'lodash.debounce'; import * as throttle from 'lodash.throttle'; import './async-table-filter.sass'; @@ -30,6 +31,8 @@ export class AsyncTable { _element; _app; + _eventManager; + _asyncTableHeader; _asyncTableId; @@ -66,6 +69,8 @@ export class AsyncTable { this._element = element; this._app = app; + this._eventManager = new EventManager(); + if (this._element.classList.contains(ASYNC_TABLE_INITIALIZED_CLASS)) { return false; } @@ -144,7 +149,12 @@ export class AsyncTable { } destroy() { - console.log('TBD: Destroy AsyncTable'); + this._windowStorage.clear(); + this._historyStorage.clear(); + this._eventManager.removeAllEventListenersFromUtil(); + this._active = false; + if (this._element.classList.contains(ASYNC_TABLE_INITIALIZED_CLASS)) + this._element.classList.remove(ASYNC_TABLE_INITIALIZED_CLASS); } _startSortableHeaders() { @@ -156,7 +166,8 @@ export class AsyncTable { this._windowStorage.save('horizPos', (this._scrollTable || {}).scrollLeft); this._linkClickHandler(event); }; - th.element.addEventListener('click', th.clickHandler); + const linkClickEvent = new EventWrapper(EVENT_TYPE.CLICK, th.clickHandler.bind(this), th.element); + this._eventManager.registerNewListener(linkClickEvent); }); } @@ -179,7 +190,9 @@ export class AsyncTable { } this._linkClickHandler(event); }; - link.element.addEventListener('click', link.clickHandler); + + const clickEvent = new EventWrapper(EVENT_TYPE.CLICK, link.clickHandler.bind(this), link.element); + this._eventManager.registerNewListener(clickEvent); }); } } @@ -190,7 +203,8 @@ export class AsyncTable { if (this._pagesizeForm) { const pagesizeSelect = this._pagesizeForm.querySelector('[name=' + this._asyncTableId + '-pagesize]'); - pagesizeSelect.addEventListener('change', this._changePagesizeHandler); + const pageSizeChangeEvent = new EventWrapper(EVENT_TYPE.CHANGE, this._changePagesizeHandler.bind(this), pagesizeSelect); + this._eventManager.registerNewListener(pageSizeChangeEvent); } } @@ -254,33 +268,42 @@ export class AsyncTable { } }, INPUT_DEBOUNCE); this._cancelPendingUpdates.push(debouncedInput.cancel); - - input.addEventListener('input', () => { + + const inputHandler =() => { this._ignoreRequest = true; debouncedInput(); - }); + }; + const inputEvent = new EventWrapper(EVENT_TYPE.INPUT, inputHandler.bind(this), input ); + this._eventManager.registerNewListener(inputEvent); }); this._tableFilterInputs.change.forEach((input) => { - input.addEventListener('change', () => { + + const changeHandler = () => { //if (this._element.classList.contains(ASYNC_TABLE_LOADING_CLASS)) this._ignoreRequest = true; debouncedUpdateFromTableFilter(); - }); + }; + const changeEvent = new EventWrapper(EVENT_TYPE.CHANGE, changeHandler.bind(this), input); + this._eventManager.registerNewListener(changeEvent); }); this._tableFilterInputs.select.forEach((input) => { - input.addEventListener('change', () => { + const selectChangeHandler = () => { this._ignoreRequest = true; debouncedUpdateFromTableFilter(); - }); + }; + const selectEvent = new EventWrapper(EVENT_TYPE.CHANGE, selectChangeHandler.bind(this), input); + this._eventManager.registerNewListener(selectEvent); }); - tableFilterForm.addEventListener('submit', (event) =>{ + const submitEventHandler = (event) =>{ event.preventDefault(); this._ignoreRequest = true; debouncedUpdateFromTableFilter(); - }); + }; + const submitEvent = new EventWrapper(EVENT_TYPE.SUBMIT, submitEventHandler.bind(this), tableFilterForm); + this._eventManager.registerNewListener(submitEvent); } _updateFromTableFilter(tableFilterForm) { @@ -439,10 +462,10 @@ export class AsyncTable { ).finally(() => this._element.classList.remove(ASYNC_TABLE_LOADING_CLASS)); } - _debugLog() {} - // _debugLog(fName, ...args) { - // console.log(`[DEBUGLOG] AsyncTable.${fName}`, { args: args, instance: this }); - // } + //_debugLog() {} + _debugLog(fName, ...args) { + console.log(`[DEBUGLOG] AsyncTable.${fName}`, { args: args, instance: this }); + } } diff --git a/frontend/src/utils/async-table/async-table.spec.js b/frontend/src/utils/async-table/async-table.spec.js index 7f008ac49..61b476a9c 100644 --- a/frontend/src/utils/async-table/async-table.spec.js +++ b/frontend/src/utils/async-table/async-table.spec.js @@ -1,4 +1,4 @@ -import { AsyncTable } from './async-table'; +import { AsyncTable, ASYNC_TABLE_INITIALIZED_CLASS } from './async-table'; const AppTestMock = { httpClient: { @@ -50,4 +50,11 @@ describe('AsyncTable', () => { new AsyncTable(); }).toThrow(); }); + + it('should destroy Async Table', () => { + //asyncTable.destroy(); + asyncTable.start(); + expect(asyncTable._eventManager._registeredListeners.length).toBe(0); + expect(asyncTable._element.classList).not.toContain(ASYNC_TABLE_INITIALIZED_CLASS); + }); }); From 4a63050334569d91eea736cf3acab50a23289cfd Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Wed, 30 Jun 2021 16:59:45 +0200 Subject: [PATCH 11/81] chore(async-table): implemented destroy in async table --- frontend/src/utils/async-table/async-table.js | 3 +-- frontend/src/utils/async-table/async-table.spec.js | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/src/utils/async-table/async-table.js b/frontend/src/utils/async-table/async-table.js index 0e3c49685..68785f276 100644 --- a/frontend/src/utils/async-table/async-table.js +++ b/frontend/src/utils/async-table/async-table.js @@ -149,8 +149,7 @@ export class AsyncTable { } destroy() { - this._windowStorage.clear(); - this._historyStorage.clear(); + this._windowStorage.clear(this._windowStorage._options); this._eventManager.removeAllEventListenersFromUtil(); this._active = false; if (this._element.classList.contains(ASYNC_TABLE_INITIALIZED_CLASS)) diff --git a/frontend/src/utils/async-table/async-table.spec.js b/frontend/src/utils/async-table/async-table.spec.js index 61b476a9c..de5dc9b98 100644 --- a/frontend/src/utils/async-table/async-table.spec.js +++ b/frontend/src/utils/async-table/async-table.spec.js @@ -52,8 +52,8 @@ describe('AsyncTable', () => { }); it('should destroy Async Table', () => { - //asyncTable.destroy(); asyncTable.start(); + asyncTable.destroy(); expect(asyncTable._eventManager._registeredListeners.length).toBe(0); expect(asyncTable._element.classList).not.toContain(ASYNC_TABLE_INITIALIZED_CLASS); }); From c5760bb2ebacb678363bbd88b5a4d1f7992954f3 Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Wed, 30 Jun 2021 17:00:35 +0200 Subject: [PATCH 12/81] chore(check-all): implemented destroy in check-all --- frontend/src/utils/check-all/check-all.js | 30 +++++++++++++++---- .../src/utils/check-all/check-all.spec.js | 10 ++++++- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/frontend/src/utils/check-all/check-all.js b/frontend/src/utils/check-all/check-all.js index b9796eeb5..df151bc62 100644 --- a/frontend/src/utils/check-all/check-all.js +++ b/frontend/src/utils/check-all/check-all.js @@ -2,6 +2,7 @@ const DEBUG_MODE = /localhost/.test(window.location.href) ? 0 : 0; import { Utility } from '../../core/utility'; import { TableIndices } from '../../lib/table/table'; +import { EventManager, EventWrapper, EVENT_TYPE } from '../../lib/event-manager/event-manager'; const CHECKBOX_SELECTOR = '[type="checkbox"]'; @@ -13,6 +14,8 @@ const CHECK_ALL_INITIALIZED_CLASS = 'check-all--initialized'; export class CheckAll { _element; + _eventManager; + _columns = new Array(); _checkAllColumns = new Array(); @@ -25,6 +28,8 @@ export class CheckAll { this._element = element; + this._eventManager = new EventManager(); + if (this._element.classList.contains(CHECK_ALL_INITIALIZED_CLASS)) { return false; } @@ -42,6 +47,16 @@ export class CheckAll { this._element.classList.add(CHECK_ALL_INITIALIZED_CLASS); } + destroy() { + this._eventManager.removeAllEventListenersFromUtil(); + this._checkAllColumns.forEach((column) => { + column._checkAllCheckBox.remove(); + }); + + if(this._element.classList.contains(CHECK_ALL_INITIALIZED_CLASS)) + this._element.classList.remove(CHECK_ALL_INITIALIZED_CLASS); + } + _gatherColumns() { for (const rowIndex of Array(this._tableIndices.maxRow + 1).keys()) { for (const colIndex of Array(this._tableIndices.maxCol + 1).keys()) { @@ -81,14 +96,17 @@ class CheckAllColumn { _app; _table; _column; + + _eventManager _checkAllCheckbox; _checkboxId = 'check-all-checkbox-' + Math.floor(Math.random() * 100000); - constructor(table, app, column) { + constructor(table, app, column, eventManager) { this._column = column; this._table = table; this._app = app; + this._eventManager = eventManager; const th = this._column.filter(element => element.tagName == 'TH')[0]; if (!th) @@ -102,7 +120,8 @@ class CheckAllColumn { // set up new checkbox this._app.utilRegistry.initAll(th); - this._checkAllCheckbox.addEventListener('input', this._onCheckAllCheckboxInput.bind(this)); + const checkBoxInputEvent = new EventWrapper(EVENT_TYPE.INPUT, this._onCheckAllCheckboxInput.bind(this), this._checkAllCheckbox); + this._eventManager.registerNewListener(checkBoxInputEvent); this._setupCheckboxListeners(); } @@ -113,9 +132,10 @@ class CheckAllColumn { _setupCheckboxListeners() { this._column .flatMap(cell => cell.tagName == 'TH' ? new Array() : Array.from(cell.querySelectorAll(CHECKBOX_SELECTOR))) - .forEach(checkbox => - checkbox.addEventListener('input', this._updateCheckAllCheckboxState.bind(this)) - ); + .forEach(checkbox => { + const checkBoxUpdateEvent = new EventWrapper(EVENT_TYPE.INPUT, this._updateCheckAllCheckboxState.bind(this), checkbox); + this._eventManager.registerNewListener(checkBoxUpdateEvent); + }); } _updateCheckAllCheckboxState() { diff --git a/frontend/src/utils/check-all/check-all.spec.js b/frontend/src/utils/check-all/check-all.spec.js index 9b8d30f77..08cf2a088 100644 --- a/frontend/src/utils/check-all/check-all.spec.js +++ b/frontend/src/utils/check-all/check-all.spec.js @@ -1,4 +1,4 @@ -import { CheckAll } from './check-all'; +import { CheckAll, CHECK_ALL_INITIALIZED_CLASS } from './check-all'; const MOCK_APP = { utilRegistry: { @@ -24,4 +24,12 @@ describe('CheckAll', () => { new CheckAll(); }).toThrow(); }); + + it('should destroy CheckAll', () => { + checkAll.destroy(); + expect(checkAll._eventManager._registeredListeners.length).toBe(0); + console.log(checkAll._element.classList); + expect(checkAll._element.classList).not.toEqual(jasmine.arrayContaining([CHECK_ALL_INITIALIZED_CLASS])); + }); + }); From 6d66b822ac59cf751a2c67bfa366494c91ce04bf Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Wed, 30 Jun 2021 17:01:20 +0200 Subject: [PATCH 13/81] chore(course-teaser): implemented destroy in course teaser --- .../src/utils/course-teaser/course-teaser.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/frontend/src/utils/course-teaser/course-teaser.js b/frontend/src/utils/course-teaser/course-teaser.js index 0419dcbda..95a49faee 100644 --- a/frontend/src/utils/course-teaser/course-teaser.js +++ b/frontend/src/utils/course-teaser/course-teaser.js @@ -1,4 +1,5 @@ import { Utility } from '../../core/utility'; +import { EventManager, EventWrapper, EVENT_TYPE } from '../../lib/event-manager/event-manager'; import './course-teaser.sass'; const COURSE_TEASER_INITIALIZED_CLASS = 'course-teaser--initialized'; @@ -12,16 +13,30 @@ const COURSE_TEASER_CHEVRON_CLASS = 'course-teaser__chevron'; export class CourseTeaser { _element; + _eventManager constructor(element) { if (!element) { throw new Error('CourseTeaser utility cannot be setup without an element!'); } + this._eventManager = new EventManager(); if (element.classList.contains(COURSE_TEASER_INITIALIZED_CLASS)) { return false; } this._element = element; - element.addEventListener('click', e => this._onToggleExpand(e)); + const clickHandler = e => this._onToggleExpand(e); + const clickEvent = new EventWrapper(EVENT_TYPE.CLICK, clickHandler.bind(this), element); + this._eventManager.registerNewListener(clickEvent); + } + + destroy() { + this._eventManager.removeAllEventListenersFromUtil(); + if(this._element.classList.contains(COURSE_TEASER_EXPANDED_CLASS)) { + this._element.classList.remove(COURSE_TEASER_EXPANDED_CLASS); + } + if (this._element.classList.contains(COURSE_TEASER_INITIALIZED_CLASS)) { + this._element.classList.remove(COURSE_TEASER_INITIALIZED_CLASS); + } } _onToggleExpand(event) { From 780a5f7ce17fbe419cedc077e196e3999c929212 Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Fri, 2 Jul 2021 15:27:33 +0200 Subject: [PATCH 14/81] chore(exam-correct): destroy method in exam-correct implemented --- .../src/lib/event-manager/event-manager.js | 1 + .../src/utils/check-all/check-all.spec.js | 1 - .../src/utils/exam-correct/exam-correct.js | 42 ++++++++++++------- 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/frontend/src/lib/event-manager/event-manager.js b/frontend/src/lib/event-manager/event-manager.js index a1400486a..f0959d66e 100644 --- a/frontend/src/lib/event-manager/event-manager.js +++ b/frontend/src/lib/event-manager/event-manager.js @@ -7,6 +7,7 @@ export const EVENT_TYPE = { MOUSE_OVER : 'mouseover', SUBMIT : 'submit', INPUT : 'input', + FOCUS_OUT : 'focusout', //more to be added }; diff --git a/frontend/src/utils/check-all/check-all.spec.js b/frontend/src/utils/check-all/check-all.spec.js index 08cf2a088..431dd5993 100644 --- a/frontend/src/utils/check-all/check-all.spec.js +++ b/frontend/src/utils/check-all/check-all.spec.js @@ -28,7 +28,6 @@ describe('CheckAll', () => { it('should destroy CheckAll', () => { checkAll.destroy(); expect(checkAll._eventManager._registeredListeners.length).toBe(0); - console.log(checkAll._element.classList); expect(checkAll._element.classList).not.toEqual(jasmine.arrayContaining([CHECK_ALL_INITIALIZED_CLASS])); }); diff --git a/frontend/src/utils/exam-correct/exam-correct.js b/frontend/src/utils/exam-correct/exam-correct.js index cf23c911d..c88ce2d6b 100644 --- a/frontend/src/utils/exam-correct/exam-correct.js +++ b/frontend/src/utils/exam-correct/exam-correct.js @@ -1,5 +1,6 @@ import { Utility } from '../../core/utility'; import { StorageManager, LOCATION } from '../../lib/storage-manager/storage-manager'; +import { EventManager, EventWrapper, EVENT_TYPE } from '../../lib/event-manager/event-manager'; import { HttpClient } from '../../services/http-client/http-client'; import moment from 'moment'; @@ -58,6 +59,7 @@ export class ExamCorrect { _lastColumnIndex; _storageManager; + _eventManager; constructor(element, app) { if (!element) { @@ -71,6 +73,8 @@ export class ExamCorrect { this._element = element; this._app = app; + this._eventManager = new EventManager(); + // TODO work in progress // this._storageManager = new StorageManager('EXAM_CORRECT', '0.0.0', { location: LOCATION.SESSION, encryption: { all: { tag: 'exam-correct', exam: this._element.getAttribute('uw-exam-correct') } } }); this._storageManager = new StorageManager('EXAM_CORRECT', '0.0.0', { location: LOCATION.WINDOW }); @@ -88,20 +92,28 @@ export class ExamCorrect { this._resultPassSelect = resultDetailCell && resultDetailCell.querySelector('select.uw-exam-correct__pass'); this._partDeleteBoxes = [...this._element.querySelectorAll('input.uw-exam-correct--delete-exam-part')]; - if (this._sendBtn) - this._sendBtn.addEventListener('click', this._sendCorrectionHandler.bind(this)); - else console.error('ExamCorrect utility could not detect send button!'); + if (this._sendBtn){ + const sendClickEvent = new EventWrapper(EVENT_TYPE.CLICK, this._sendCorrectionHandler.bind(this), this._sendBtn); + this._eventManager.registerNewListener(sendClickEvent); + } else { + console.error('ExamCorrect utility could not detect send button!'); + } - if (this._userInput) - this._userInput.addEventListener('focusout', this._validateUserInput.bind(this)); - else throw new Error('ExamCorrect utility could not detect user input!'); + if (this._userInput) { + const focusOutEvent = new EventWrapper(EVENT_TYPE.FOCUS_OUT, this._validateUserInput.bind(this), this._userInput); + this._eventManager.registerNewListener(focusOutEvent); + } else { + throw new Error('ExamCorrect utility could not detect user input!'); + } for (let deleteBox of this._partDeleteBoxes) { - deleteBox.addEventListener('change', (() => { this._updatePartDeleteDisabled(deleteBox); }).bind(this)); + const deleteBoxChangeEvent = new EventWrapper(EVENT_TYPE.CHANGE, (() => { this._updatePartDeleteDisabled(deleteBox); }).bind(this), deleteBox); + this._eventManger.registerNewListener(deleteBoxChangeEvent); } for (let input of [this._userInput, ...this._partInputs]) { - input.addEventListener('keypress', this._inputKeypress.bind(this)); + const inputKeyDownEvent = new EventWrapper(EVENT_TYPE.KEYDOWN, this._inputKeypress.bind(this), input); + this._eventManager.registerNewListener(inputKeyDownEvent); } if (!this._userInputStatus) { @@ -125,23 +137,25 @@ export class ExamCorrect { ); if (this._resultSelect && this._resultGradeSelect) { - this._resultSelect.addEventListener('change', () => { + const resultSelectEvent = new EventWrapper(EVENT_TYPE.CHANGE, (() => { if (this._resultSelect.value !== 'grade') this._resultGradeSelect.classList.add('grade-hidden'); else this._resultGradeSelect.classList.remove('grade-hidden'); - }); + }).bind(this), this._resultSelect ); + this._eventManager.registerNewListener(resultSelectEvent); if (this._resultSelect.value !== 'grade') this._resultGradeSelect.classList.add('grade-hidden'); } if (this._resultSelect && this._resultPassSelect) { - this._resultSelect.addEventListener('change', () => { + const resultPassSelectEvent = new EventWrapper(EVENT_TYPE.CHANGE, (() => { if (this._resultSelect.value !== 'pass') this._resultPassSelect.classList.add('pass-hidden'); else this._resultPassSelect.classList.remove('pass-hidden'); - }); + }).bind(this), this._resultSelect); + this._eventManager.registerNewListener(resultPassSelectEvent); if (this._resultSelect.value !== 'pass') this._resultPassSelect.classList.add('pass-hidden'); @@ -158,9 +172,7 @@ export class ExamCorrect { } destroy() { - this._sendBtn.removeEventListener('click', this._sendCorrectionHandler); - this._userInput.removeEventListener('change', this._validateUserInput); - // TODO destroy handlers on user input candidate elements + this._eventManager.removeAllEventListenersFromUtil(); } _updatePartDeleteDisabled(deleteBox) { From 3e01c8d9102af258502b2d6352503853109b25ea Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Fri, 2 Jul 2021 15:40:53 +0200 Subject: [PATCH 15/81] chore(auto-submit-button): implemented destroy in auto-submit-button --- frontend/src/utils/form/auto-submit-button.js | 5 +++- .../src/utils/form/auto-submit-button.spec.js | 27 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 frontend/src/utils/form/auto-submit-button.spec.js diff --git a/frontend/src/utils/form/auto-submit-button.js b/frontend/src/utils/form/auto-submit-button.js index bf7544d30..77e942f28 100644 --- a/frontend/src/utils/form/auto-submit-button.js +++ b/frontend/src/utils/form/auto-submit-button.js @@ -9,8 +9,10 @@ const AUTO_SUBMIT_BUTTON_HIDDEN_CLASS = 'hidden'; selector: AUTO_SUBMIT_BUTTON_UTIL_SELECTOR, }) export class AutoSubmitButton { + _element; constructor(element) { + this._element = element; if (!element) { throw new Error('Auto Submit Button utility needs to be passed an element!'); } @@ -24,6 +26,7 @@ export class AutoSubmitButton { } destroy() { - // TODO + this._element.classList.remove(AUTO_SUBMIT_BUTTON_INITIALIZED_CLASS); + this._element.classList.remove(AUTO_SUBMIT_BUTTON_HIDDEN_CLASS); } } diff --git a/frontend/src/utils/form/auto-submit-button.spec.js b/frontend/src/utils/form/auto-submit-button.spec.js new file mode 100644 index 000000000..b1436c867 --- /dev/null +++ b/frontend/src/utils/form/auto-submit-button.spec.js @@ -0,0 +1,27 @@ +import { AutoSubmitButton, AUTO_SUBMIT_BUTTON_INITIALIZED_CLASS, AUTO_SUBMIT_BUTTON_HIDDEN_CLASS } from './auto-submit-button.js'; + +describe('Auto-submit-button', () => { + + let autoSubmitButton; + + beforeEach(() => { + const element = document.createElement('div'); + autoSubmitButton = new AutoSubmitButton(element); + }); + + it('should create', () => { + expect(autoSubmitButton).toBeTruthy(); + }); + + it('should destory auto-submit-button', () => { + autoSubmitButton.destroy(); + expect(autoSubmitButton._element.classList).not.toContain(AUTO_SUBMIT_BUTTON_INITIALIZED_CLASS); + expect(autoSubmitButton._element.classList).not.toContain(AUTO_SUBMIT_BUTTON_HIDDEN_CLASS); + }); + + it('should throw if called without an element', () => { + expect(() => { + new AutoSubmitButton(); + }).toThrow(); + }); + }); \ No newline at end of file From a75f6621ce56aabd52241fa9ee91b61886cb409e Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Fri, 2 Jul 2021 16:43:53 +0200 Subject: [PATCH 16/81] chore(auto-submit-input): implemented destroy in auto-submit-input --- frontend/src/utils/form/auto-submit-input.js | 13 ++++++-- .../src/utils/form/auto-submit-input.spec.js | 30 +++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 frontend/src/utils/form/auto-submit-input.spec.js diff --git a/frontend/src/utils/form/auto-submit-input.js b/frontend/src/utils/form/auto-submit-input.js index f442f2960..8ec7e869e 100644 --- a/frontend/src/utils/form/auto-submit-input.js +++ b/frontend/src/utils/form/auto-submit-input.js @@ -1,5 +1,6 @@ import * as debounce from 'lodash.debounce'; import { Utility } from '../../core/utility'; +import { EventManager, EventWrapper, EVENT_TYPE } from '../../lib/event-manager/event-manager'; export const AUTO_SUBMIT_INPUT_UTIL_SELECTOR = '[uw-auto-submit-input]'; @@ -12,6 +13,8 @@ export class AutoSubmitInput { _element; + _eventManager; + _form; _debouncedHandler; @@ -22,6 +25,8 @@ export class AutoSubmitInput { this._element = element; + this._eventManager = new EventManager(); + if (this._element.classList.contains(AUTO_SUBMIT_INPUT_INITIALIZED_CLASS)) { return false; } @@ -33,12 +38,16 @@ export class AutoSubmitInput { this._debouncedHandler = debounce(this.autoSubmit, 500); - this._element.addEventListener('input', this._debouncedHandler); + const inputEvent = new EventWrapper(EVENT_TYPE.INPUT, this._debouncedHandler.bind(this), this._element); + this._eventManager.registerNewListener(inputEvent); + this._element.classList.add(AUTO_SUBMIT_INPUT_INITIALIZED_CLASS); } destroy() { - this._element.removeEventListener('input', this._debouncedHandler); + this._eventManager.removeAllEventListenersFromUtil(); + if(this._element.classList.contains(AUTO_SUBMIT_INPUT_INITIALIZED_CLASS)) + this._element.classList.remove(AUTO_SUBMIT_INPUT_INITIALIZED_CLASS); } autoSubmit = () => { diff --git a/frontend/src/utils/form/auto-submit-input.spec.js b/frontend/src/utils/form/auto-submit-input.spec.js new file mode 100644 index 000000000..26c59cdd2 --- /dev/null +++ b/frontend/src/utils/form/auto-submit-input.spec.js @@ -0,0 +1,30 @@ +import { AutoSubmitInput, AUTO_SUBMIT_INPUT_INITIALIZED_CLASS } from './auto-submit-input.js'; + +describe('Auto-submit-input', () => { + + let autoSubmitInput; + + beforeEach(() => { + const form = document.createElement('form'); + const element = document.createElement('input'); + element.setAttribute('type', 'text'); + form.append(element); + autoSubmitInput = new AutoSubmitInput(element); + }); + + it('should create', () => { + expect(autoSubmitInput).toBeTruthy(); + }); + + it('should destory auto-submit-button', () => { + autoSubmitInput.destroy(); + expect(autoSubmitInput._eventManager._registeredListeners.length).toBe(0); + expect(autoSubmitInput._element.classList).not.toEqual(jasmine.arrayContaining([AUTO_SUBMIT_INPUT_INITIALIZED_CLASS])); + }); + + it('should throw if called without an element', () => { + expect(() => { + new AutoSubmitInput(); + }).toThrow(); + }); + }); \ No newline at end of file From 6581137d2cddf696456b8e894c04a3a4614104dc Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Mon, 5 Jul 2021 10:47:04 +0200 Subject: [PATCH 17/81] chore(communication-recipients): implemented destroy method --- .../utils/form/communication-recipients.js | 40 ++++++++++++++----- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/frontend/src/utils/form/communication-recipients.js b/frontend/src/utils/form/communication-recipients.js index 2a9367f47..cf28715eb 100644 --- a/frontend/src/utils/form/communication-recipients.js +++ b/frontend/src/utils/form/communication-recipients.js @@ -1,4 +1,5 @@ import { Utility } from '../../core/utility'; +import { EventManager, EventWrapper, EVENT_TYPE } from '../../lib/event-manager/event-manager'; const MASS_INPUT_SELECTOR = '.massinput'; const RECIPIENT_CATEGORIES_SELECTOR = '.recipient-categories'; @@ -14,12 +15,16 @@ const RECIPIENT_CATEGORY_CHECKED_COUNTER_SELECTOR = '.recipient-category__checke }) export class CommunicationRecipients { massInputElement; + _element; + + _eventManager; constructor(element) { if (!element) { throw new Error('Communication Recipient utility cannot be setup without an element!'); } - + this._element = element; + this._eventManager = new EventManager(); this.massInputElement = element.closest(MASS_INPUT_SELECTOR); this.setupRecipientCategories(); @@ -31,6 +36,18 @@ export class CommunicationRecipients { setupRecipientCategories() { Array.from(this.massInputElement.querySelectorAll(RECIPIENT_CATEGORY_SELECTOR)).forEach(setupRecipientCategory); } + + removeCheckedCounter() { + let checkedCounters = this._element.querySelectorAll(RECIPIENT_CATEGORY_CHECKED_COUNTER_SELECTOR); + checkedCounters.forEach((checkedCounter) => { + checkedCounter.innerHTML = ''; + }); + } + + destroy() { + this._eventManager.removeAllEventListenersFromUtil(); + this.removeCheckedCounter(); + } } function setupRecipientCategory(recipientCategoryElement) { @@ -42,34 +59,35 @@ function setupRecipientCategory(recipientCategoryElement) { const toggleAllCheckbox = recipientCategoryElement.querySelector(RECIPIENT_CATEGORY_TOGGLE_ALL_SELECTOR); // setup category checkbox to toggle all child checkboxes if changed - categoryCheckbox.addEventListener('change', () => { + const categoryToggleEvent = new EventWrapper(EVENT_TYPE.CHANGE,(() => { categoryCheckboxes.filter(checkbox => !checkbox.disabled).forEach(checkbox => { checkbox.checked = categoryCheckbox.checked; }); updateCheckedCounter(recipientCategoryElement, categoryCheckboxes); updateToggleAllCheckbox(toggleAllCheckbox, categoryCheckboxes); - }); + }).bind(this), categoryCheckbox ); + this._eventManager.registerNewListener(categoryToggleEvent); // update counter and toggle checkbox initially updateCheckedCounter(recipientCategoryElement, categoryCheckboxes); updateToggleAllCheckbox(toggleAllCheckbox, categoryCheckboxes); // register change listener for individual checkboxes - categoryCheckboxes.forEach(checkbox => { - checkbox.addEventListener('change', () => { - updateCheckedCounter(recipientCategoryElement, categoryCheckboxes); - updateToggleAllCheckbox(toggleAllCheckbox, categoryCheckboxes); - }); - }); + const individualToggleEvent = new EventWrapper(EVENT_TYPE.CHANGE,(() => { + updateCheckedCounter(recipientCategoryElement, categoryCheckboxes); + updateToggleAllCheckbox(toggleAllCheckbox, categoryCheckboxes); + }).bind(this), categoryCheckboxes ); + this._eventManager.registerNewListener(individualToggleEvent); // register change listener for toggle all checkbox if (toggleAllCheckbox) { - toggleAllCheckbox.addEventListener('change', () => { + const toggleAllEvent = new EventWrapper(EVENT_TYPE.CHANG, (() => { categoryCheckboxes.filter(checkbox => !checkbox.disabled).forEach(checkbox => { checkbox.checked = toggleAllCheckbox.checked; }); updateCheckedCounter(recipientCategoryElement, categoryCheckboxes); - }); + }).bind(this), toggleAllCheckbox); + this._eventManager.registerNewListener(toggleAllEvent); } } } From 34b4f48386c8fff4569b12eb3f7919e7c77d33c0 Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Mon, 5 Jul 2021 12:01:27 +0200 Subject: [PATCH 18/81] feat(event-manager): mutation observers can be managed via the event manager --- frontend/src/lib/event-manager/event-manager.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/frontend/src/lib/event-manager/event-manager.js b/frontend/src/lib/event-manager/event-manager.js index f0959d66e..a091445d5 100644 --- a/frontend/src/lib/event-manager/event-manager.js +++ b/frontend/src/lib/event-manager/event-manager.js @@ -15,10 +15,12 @@ export const EVENT_TYPE = { export class EventManager { _registeredListeners; + _mutationObservers; constructor() { this._registeredListeners = []; + this._mutationObservers = []; } registerNewListener(eventWrapper) { @@ -28,6 +30,12 @@ export class EventManager { this._registeredListeners.push(eventWrapper); } + registerNewMutationObserver(callback, domNode, config) { + let observer = new MutationObserver(callback); + observer.observe(domNode, config); + this._muatationObservers.add(observer); + } + removeAllEventListenersFromUtil() { this._debugLog('removeAllEventListenersFromUtil',); for (let eventWrapper of this._registeredListeners) { @@ -37,6 +45,10 @@ export class EventManager { this._registeredListeners = []; } + removeAllObserversFromUtil() { + this._mutationObservers.forEach((observer) => observer.disconnect()); + } + //Todo: Uncomment debug log! //_debugLog() {} From 5af045c11bd1b9b13f262b1029fafd4198d5229c Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Mon, 5 Jul 2021 12:05:16 +0200 Subject: [PATCH 19/81] chore(communication-recipients): mutation observer is managed via eventManager --- frontend/src/utils/form/communication-recipients.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/utils/form/communication-recipients.js b/frontend/src/utils/form/communication-recipients.js index cf28715eb..7848a5736 100644 --- a/frontend/src/utils/form/communication-recipients.js +++ b/frontend/src/utils/form/communication-recipients.js @@ -29,8 +29,7 @@ export class CommunicationRecipients { this.setupRecipientCategories(); - const recipientObserver = new MutationObserver(this.setupRecipientCategories.bind(this)); - recipientObserver.observe(this.massInputElement, { childList: true }); + this._eventManager.registerNewMutationObserver(this.setupRecipientCategories.bind(this), this.massInputElement, { childList: true }); } setupRecipientCategories() { @@ -46,6 +45,7 @@ export class CommunicationRecipients { destroy() { this._eventManager.removeAllEventListenersFromUtil(); + this._eventManager.removeAllObserversFromUtil(); this.removeCheckedCounter(); } } From 9e342a5368a69122f3d779a500052e1953313027 Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Mon, 5 Jul 2021 15:00:44 +0200 Subject: [PATCH 20/81] chore(datpicker): implemented destroy method --- frontend/src/utils/form/datepicker.js | 36 +++++++++++++++++---------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/frontend/src/utils/form/datepicker.js b/frontend/src/utils/form/datepicker.js index 21b09eb19..7a8abe3ad 100644 --- a/frontend/src/utils/form/datepicker.js +++ b/frontend/src/utils/form/datepicker.js @@ -2,6 +2,7 @@ import datetime from 'tail.datetime'; import './datepicker.css'; import { Utility } from '../../core/utility'; import moment from 'moment'; +import { EventManager, EventWrapper, EVENT_TYPE } from '../../lib/event-manager/event-manager'; import * as defer from 'lodash.defer'; @@ -82,6 +83,8 @@ export class Datepicker { initialValue; _locale; + _eventManager; + _unloadIsDueToSubmit = false; constructor(element) { @@ -102,6 +105,8 @@ export class Datepicker { this._element = element; + this._eventManager = new EventManager(); + // store the previously set type to select the input format this.elementType = this._element.getAttribute('type'); @@ -179,23 +184,22 @@ export class Datepicker { } // reregister change event to prevent event loop - this._element.addEventListener('change', setDatepickerDate, { once: true }); }; // change the selected date in the tail.datetime instance if the value of the input element is changed - this._element.addEventListener('change', setDatepickerDate, { once: true }); - + const changeSelectedDateEvent = new EventWrapper(EVENT_TYPE.CHANGE, setDatepickerDate.bind(this), this._element, { once: true }); + this._eventManager.registerNewListener(changeSelectedDateEvent); // create a mutation observer that observes the datepicker instance class and sets // the datepicker-open DOM attribute of the input element if the datepicker has been opened - const datepickerInstanceObserver = new MutationObserver((mutations) => { + let callback = (mutations) => { for (const mutation of mutations) { if (!mutation.oldValue.includes(DATEPICKER_OPEN_CLASS) && this.datepickerInstance.dt.getAttribute('class').includes(DATEPICKER_OPEN_CLASS)) { this._element.setAttribute(ATTR_DATEPICKER_OPEN, true); break; } } - }); - datepickerInstanceObserver.observe(this.datepickerInstance.dt, { + }; + this._eventManager.registerNewMutationObserver(callback.bind(this), this.datepickerInstance.dt, { attributes: true, attributeFilter: ['class'], attributeOldValue: true, @@ -203,38 +207,44 @@ export class Datepicker { // close the instance on focusout of any element if another input is focussed that is neither the timepicker nor _element - window.addEventListener('focusout', event => { + const focusOutEvent = new EventWrapper(EVENT_TYPE.FOCUS_OUT,(event => { const hasFocus = event.relatedTarget !== null; const focussedIsNotTimepicker = !this.datepickerInstance.dt.contains(event.relatedTarget); const focussedIsNotElement = event.relatedTarget !== this._element; const focussedIsInDocument = window.document.contains(event.relatedTarget); if (hasFocus && focussedIsNotTimepicker && focussedIsNotElement && focussedIsInDocument) this.closeDatepickerInstance(); - }); + }).bind(this), window ); + this._eventManager.registerNewListener(focusOutEvent); // close the instance on click on any element outside of the datepicker (except the input element itself) - window.addEventListener('click', event => { + const clickOutsideEvent = new EventWrapper(EVENT_TYPE.CLICK, (event => { const targetIsOutside = !this.datepickerInstance.dt.contains(event.target) && event.target !== this.datepickerInstance.dt; const targetIsInDocument = window.document.contains(event.target); const targetIsNotElement = event.target !== this._element; if (targetIsOutside && targetIsInDocument && targetIsNotElement) this.closeDatepickerInstance(); - }); + }).bind(this), window); + this._eventManager.registerNewListener(clickOutsideEvent); // close the instance on escape keydown events - this._element.addEventListener('keydown', event => { + const escapeCloseEvent = new EventWrapper(EVENT_TYPE.KEYDOWN, (event => { if (event.keyCode === KEYCODE_ESCAPE) { this.closeDatepickerInstance(); } - }); + }).bind(this), this._element); + this._eventManager.registerNewListener(escapeCloseEvent); // format the date value of the form input element of this datepicker before form submission - this._element.form.addEventListener('submit', this._submitHandler.bind(this)); + const submitEvent = new EventWrapper(EVENT_TYPE.SUBMIT, this._submitHandler.bind(this), this._element.form); + this._eventManager.registerNewListener(submitEvent); } destroy() { this.datepickerInstance.remove(); + this._eventManager.removeAllListenersFromUtil(); + this._element.classList.remove(DATEPICKER_INITIALIZED_CLASS); } From 03ac80342e0f3fcb1db2adf34f86a2c0d8fabf0f Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Thu, 8 Jul 2021 12:17:25 +0200 Subject: [PATCH 21/81] fix(communication-recipients): fixed undefined error with context and a few minor issues --- .../src/lib/event-manager/event-manager.js | 2 +- .../utils/form/communication-recipients.js | 81 ++++++++++--------- 2 files changed, 43 insertions(+), 40 deletions(-) diff --git a/frontend/src/lib/event-manager/event-manager.js b/frontend/src/lib/event-manager/event-manager.js index a091445d5..16bc9daa9 100644 --- a/frontend/src/lib/event-manager/event-manager.js +++ b/frontend/src/lib/event-manager/event-manager.js @@ -33,7 +33,7 @@ export class EventManager { registerNewMutationObserver(callback, domNode, config) { let observer = new MutationObserver(callback); observer.observe(domNode, config); - this._muatationObservers.add(observer); + this._mutationObservers.push(observer); } removeAllEventListenersFromUtil() { diff --git a/frontend/src/utils/form/communication-recipients.js b/frontend/src/utils/form/communication-recipients.js index 7848a5736..dc04f17cc 100644 --- a/frontend/src/utils/form/communication-recipients.js +++ b/frontend/src/utils/form/communication-recipients.js @@ -25,7 +25,7 @@ export class CommunicationRecipients { } this._element = element; this._eventManager = new EventManager(); - this.massInputElement = element.closest(MASS_INPUT_SELECTOR); + this.massInputElement = this._element.closest(MASS_INPUT_SELECTOR); this.setupRecipientCategories(); @@ -33,10 +33,10 @@ export class CommunicationRecipients { } setupRecipientCategories() { - Array.from(this.massInputElement.querySelectorAll(RECIPIENT_CATEGORY_SELECTOR)).forEach(setupRecipientCategory); + Array.from(this.massInputElement.querySelectorAll(RECIPIENT_CATEGORY_SELECTOR)).forEach(this.setupRecipientCategory.bind(this)); } - removeCheckedCounter() { + _removeCheckedCounter() { let checkedCounters = this._element.querySelectorAll(RECIPIENT_CATEGORY_CHECKED_COUNTER_SELECTOR); checkedCounters.forEach((checkedCounter) => { checkedCounter.innerHTML = ''; @@ -46,48 +46,51 @@ export class CommunicationRecipients { destroy() { this._eventManager.removeAllEventListenersFromUtil(); this._eventManager.removeAllObserversFromUtil(); - this.removeCheckedCounter(); + this._removeCheckedCounter(); } -} -function setupRecipientCategory(recipientCategoryElement) { - const categoryCheckbox = recipientCategoryElement.querySelector(RECIPIENT_CATEGORY_CHECKBOX_SELECTOR); - const categoryOptions = recipientCategoryElement.querySelector(RECIPIENT_CATEGORY_OPTIONS_SELECTOR); - if (categoryOptions) { - const categoryCheckboxes = Array.from(categoryOptions.querySelectorAll('[type="checkbox"]')); - const toggleAllCheckbox = recipientCategoryElement.querySelector(RECIPIENT_CATEGORY_TOGGLE_ALL_SELECTOR); - - // setup category checkbox to toggle all child checkboxes if changed - const categoryToggleEvent = new EventWrapper(EVENT_TYPE.CHANGE,(() => { - categoryCheckboxes.filter(checkbox => !checkbox.disabled).forEach(checkbox => { - checkbox.checked = categoryCheckbox.checked; - }); - updateCheckedCounter(recipientCategoryElement, categoryCheckboxes); - updateToggleAllCheckbox(toggleAllCheckbox, categoryCheckboxes); - }).bind(this), categoryCheckbox ); - this._eventManager.registerNewListener(categoryToggleEvent); - - // update counter and toggle checkbox initially - updateCheckedCounter(recipientCategoryElement, categoryCheckboxes); - updateToggleAllCheckbox(toggleAllCheckbox, categoryCheckboxes); - - // register change listener for individual checkboxes - const individualToggleEvent = new EventWrapper(EVENT_TYPE.CHANGE,(() => { - updateCheckedCounter(recipientCategoryElement, categoryCheckboxes); - updateToggleAllCheckbox(toggleAllCheckbox, categoryCheckboxes); - }).bind(this), categoryCheckboxes ); - this._eventManager.registerNewListener(individualToggleEvent); - - // register change listener for toggle all checkbox - if (toggleAllCheckbox) { - const toggleAllEvent = new EventWrapper(EVENT_TYPE.CHANG, (() => { + setupRecipientCategory(recipientCategoryElement) { + const categoryCheckbox = recipientCategoryElement.querySelector(RECIPIENT_CATEGORY_CHECKBOX_SELECTOR); + const categoryOptions = recipientCategoryElement.querySelector(RECIPIENT_CATEGORY_OPTIONS_SELECTOR); + + if (categoryOptions) { + const categoryCheckboxes = Array.from(categoryOptions.querySelectorAll('[type="checkbox"]')); + const toggleAllCheckbox = recipientCategoryElement.querySelector(RECIPIENT_CATEGORY_TOGGLE_ALL_SELECTOR); + + // setup category checkbox to toggle all child checkboxes if changed + const categoryToggleEvent = new EventWrapper(EVENT_TYPE.CHANGE,(() => { categoryCheckboxes.filter(checkbox => !checkbox.disabled).forEach(checkbox => { - checkbox.checked = toggleAllCheckbox.checked; + checkbox.checked = categoryCheckbox.checked; }); updateCheckedCounter(recipientCategoryElement, categoryCheckboxes); - }).bind(this), toggleAllCheckbox); - this._eventManager.registerNewListener(toggleAllEvent); + updateToggleAllCheckbox(toggleAllCheckbox, categoryCheckboxes); + }).bind(this), categoryCheckbox ); + this._eventManager.registerNewListener(categoryToggleEvent); + + // update counter and toggle checkbox initially + updateCheckedCounter(recipientCategoryElement, categoryCheckboxes); + updateToggleAllCheckbox(toggleAllCheckbox, categoryCheckboxes); + + // register change listener for individual checkboxes + categoryCheckboxes.forEach(checkbox => { + const individualToggleEvent = new EventWrapper(EVENT_TYPE.CHANGE, (() => { + updateCheckedCounter(recipientCategoryElement, categoryCheckboxes); + updateToggleAllCheckbox(toggleAllCheckbox, categoryCheckboxes); + }).bind(this), checkbox); + this._eventManager.registerNewListener(individualToggleEvent); + }); + + // register change listener for toggle all checkbox + if (toggleAllCheckbox) { + const toggleAllEvent = new EventWrapper(EVENT_TYPE.CHANGE, (() => { + categoryCheckboxes.filter(checkbox => !checkbox.disabled).forEach(checkbox => { + checkbox.checked = toggleAllCheckbox.checked; + }); + updateCheckedCounter(recipientCategoryElement, categoryCheckboxes); + }).bind(this), toggleAllCheckbox); + this._eventManager.registerNewListener(toggleAllEvent); + } } } } From edc998288a42957ebf6515d94ae82d694fbaad55 Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Thu, 8 Jul 2021 12:31:24 +0200 Subject: [PATCH 22/81] chore(form-error-remover): implemented destroy --- frontend/src/utils/form/form-error-remover.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/frontend/src/utils/form/form-error-remover.js b/frontend/src/utils/form/form-error-remover.js index 1b1509c2d..5fc1f90c0 100644 --- a/frontend/src/utils/form/form-error-remover.js +++ b/frontend/src/utils/form/form-error-remover.js @@ -1,4 +1,5 @@ import { Utility } from '../../core/utility'; +import { EventManager, EventWrapper, EVENT_TYPE } from '../../lib/event-manager/event-manager'; const FORM_ERROR_REMOVER_INITIALIZED_CLASS = 'form-error-remover--initialized'; const FORM_ERROR_REMOVER_INPUTS_SELECTOR = 'input:not([type="hidden"]), textarea, select'; @@ -13,6 +14,8 @@ export class FormErrorRemover { _element; + _eventManager; + constructor(element) { if (!element) throw new Error('Form Error Remover utility needs to be passed an element!'); @@ -24,6 +27,7 @@ export class FormErrorRemover { return; this._element = element; + this._eventManager = new EventManager(); this._element.classList.add(FORM_ERROR_REMOVER_INITIALIZED_CLASS); } @@ -35,11 +39,18 @@ export class FormErrorRemover { const inputElements = Array.from(this._element.querySelectorAll(FORM_ERROR_REMOVER_INPUTS_SELECTOR)); inputElements.forEach((inputElement) => { - inputElement.addEventListener('input', () => { + const inputEvent = new EventWrapper(EVENT_TYPE.INPUT, (() => { if (!inputElement.willValidate || inputElement.validity.vaild) { FORM_GROUP_WITH_ERRORS_CLASSES.forEach(c => { this._element.classList.remove(c); }); } - }); + }).bind(this), inputElement); + this._eventManager.registerNewListener(inputEvent); }); } + + destroy() { + this._eventManager.removeAllEventListenersFromUtil(); + this._element.classList.remove(FORM_ERROR_REMOVER_INITIALIZED_CLASS); + } + } From 266ceea5a84774ca5c266708b98107a93071aa02 Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Thu, 8 Jul 2021 12:39:36 +0200 Subject: [PATCH 23/81] chore(enter-is-tab): added readme.md file for util --- frontend/src/utils/form/enter-is-tab.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 frontend/src/utils/form/enter-is-tab.md diff --git a/frontend/src/utils/form/enter-is-tab.md b/frontend/src/utils/form/enter-is-tab.md new file mode 100644 index 000000000..67fd7e2c4 --- /dev/null +++ b/frontend/src/utils/form/enter-is-tab.md @@ -0,0 +1,8 @@ +# Enter is Tab Utility +When the user presses enter on a form that uses this utility, the enter is converted to a tab in order to not send the form. + +## Attribute: +`uw-enter-as-tab` + +## Example usage: + \ No newline at end of file From 40283e348b2df6b1520164ba3541d01d72c8ea3c Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Thu, 8 Jul 2021 15:53:42 +0200 Subject: [PATCH 24/81] chore(interactive-fieldset): implemented destroy method --- frontend/src/utils/form/interactive-fieldset.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/frontend/src/utils/form/interactive-fieldset.js b/frontend/src/utils/form/interactive-fieldset.js index 7e912ab90..70051b64b 100644 --- a/frontend/src/utils/form/interactive-fieldset.js +++ b/frontend/src/utils/form/interactive-fieldset.js @@ -1,4 +1,5 @@ import { Utility } from '../../core/utility'; +import { EventManager, EventWrapper, EVENT_TYPE } from '../../lib/event-manager/event-manager'; const INTERACTIVE_FIELDSET_UTIL_TARGET_SELECTOR = '.interactive-fieldset__target'; @@ -15,6 +16,8 @@ export class InteractiveFieldset { _element; + _eventManager; + conditionalInput; conditionalValue; target; @@ -28,6 +31,8 @@ export class InteractiveFieldset { this._element = element; + this._eventManger = new EventManager(); + if (this._element.classList.contains(INTERACTIVE_FIELDSET_INITIALIZED_CLASS)) { return false; } @@ -62,13 +67,11 @@ export class InteractiveFieldset { this.childInputs = Array.from(this._element.querySelectorAll(INTERACTIVE_FIELDSET_CHILD_SELECTOR)).filter(child => child.closest('[uw-interactive-fieldset]') === this._element); // add event listener - const observer = new MutationObserver(this._updateVisibility.bind(this)); - observer.observe(this.conditionalInput, { attributes: true, attributeFilter: ['data-interactive-fieldset-hidden'] }); - this.conditionalInput.addEventListener('input', this._updateVisibility.bind(this)); - + this._eventManager.registerNewMutationObserver(this._updateVisibility.bind(this), this.conditionalInput, { attributes: true, attributeFilter: ['data-interactive-fieldset-hidden'] }); + const inputEvent = new EventWrapper(EVENT_TYPE.INPUT, this._updateVisibility.bind(this), this.conditionalInput); + this._eventManager.registerNewListener(inputEvent); // mark as initialized this._element.classList.add(INTERACTIVE_FIELDSET_INITIALIZED_CLASS); - } start() { @@ -77,7 +80,8 @@ export class InteractiveFieldset { } destroy() { - // TODO + this._eventManager.cleanUp(); + this._element.classList.remove(INTERACTIVE_FIELDSET_INITIALIZED_CLASS); } _updateVisibility() { From 4c2c68327e75f5f51271853159c232fdd7bba21e Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Tue, 13 Jul 2021 12:39:51 +0200 Subject: [PATCH 25/81] fix(interactive-fieldset): small fix --- frontend/src/lib/event-manager/event-manager.js | 8 +++++++- frontend/src/utils/form/interactive-fieldset.js | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/frontend/src/lib/event-manager/event-manager.js b/frontend/src/lib/event-manager/event-manager.js index 16bc9daa9..35a5075e6 100644 --- a/frontend/src/lib/event-manager/event-manager.js +++ b/frontend/src/lib/event-manager/event-manager.js @@ -8,7 +8,7 @@ export const EVENT_TYPE = { SUBMIT : 'submit', INPUT : 'input', FOCUS_OUT : 'focusout', - //more to be added + BEFOREUNLOAD : 'beforeunload', }; @@ -47,6 +47,12 @@ export class EventManager { removeAllObserversFromUtil() { this._mutationObservers.forEach((observer) => observer.disconnect()); + this.mutationObservers = []; + } + + cleanUp() { + this.removeAllObserversFromUtil(); + this.removeAllEventListenersFromUtil(); } diff --git a/frontend/src/utils/form/interactive-fieldset.js b/frontend/src/utils/form/interactive-fieldset.js index 70051b64b..2c8ff4eb9 100644 --- a/frontend/src/utils/form/interactive-fieldset.js +++ b/frontend/src/utils/form/interactive-fieldset.js @@ -31,7 +31,7 @@ export class InteractiveFieldset { this._element = element; - this._eventManger = new EventManager(); + this._eventManager = new EventManager(); if (this._element.classList.contains(INTERACTIVE_FIELDSET_INITIALIZED_CLASS)) { return false; From 2f668c1d3ef891bc9fd682e89875d8030ea860c9 Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Tue, 13 Jul 2021 12:41:01 +0200 Subject: [PATCH 26/81] chore(navigate-away-prompt): implemnted destroy method --- frontend/src/utils/form/navigate-away-prompt.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/frontend/src/utils/form/navigate-away-prompt.js b/frontend/src/utils/form/navigate-away-prompt.js index 8890e198e..9c6a57eee 100644 --- a/frontend/src/utils/form/navigate-away-prompt.js +++ b/frontend/src/utils/form/navigate-away-prompt.js @@ -1,4 +1,5 @@ import { Utility } from '../../core/utility'; +import { EventManager, EventWrapper, EVENT_TYPE } from '../../lib/event-manager/event-manager'; import { AUTO_SUBMIT_BUTTON_UTIL_SELECTOR } from './auto-submit-button'; import { AUTO_SUBMIT_INPUT_UTIL_SELECTOR } from './auto-submit-input'; @@ -27,6 +28,8 @@ export class NavigateAwayPrompt { _element; + _eventManager; + _initFormData; _unloadDueToSubmit = false; @@ -36,6 +39,7 @@ export class NavigateAwayPrompt { } this._element = element; + this._eventManager = new EventManager(); if (this._element.classList.contains(NAVIGATE_AWAY_PROMPT_INITIALIZED_CLASS)) { return; @@ -65,15 +69,18 @@ export class NavigateAwayPrompt { return; this._initFormData = new FormData(this._element); - window.addEventListener('beforeunload', this._beforeUnloadHandler.bind(this)); + const beforeUnloadEvent = new EventWrapper(EVENT_TYPE.BEFOREUNLOAD, this._beforeUnloadHandler.bind(this), window); + this._eventManager.registerNewListener(beforeUnloadEvent); - this._element.addEventListener('submit', () => { + const submitEvent = new EventWrapper(EVENT_TYPE.SUBMIT, (() => { this._unloadDueToSubmit = true; defer(() => { this._unloadDueToSubmit = false; } ); // Restore state after event loop is settled - }); + }).bind(this), this._element); + this._eventManager.registerNewListener(submitEvent); } destroy() { + this._eventManager.cleanUp(); this._element.classList.remove(NAVIGATE_AWAY_PROMPT_INITIALIZED_CLASS); } @@ -98,8 +105,10 @@ export class NavigateAwayPrompt { // allow the event to happen if the form was not touched by the // user (i.e. if the current FormData is equal to the initial FormData) // or the unload event was initiated by a form submit - if (!formDataHasChanged || this._unloadDueToSubmit) + if (!formDataHasChanged || this._unloadDueToSubmit) { + this.destroy(); //prevent propmt from triggering after the form was closed and the user navigates away return; + } // cancel the unload event. This is the standard to force the prompt to appear. event.preventDefault(); From db30fa642333780e0d8e7ac8f6a0d9859b80729f Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Tue, 13 Jul 2021 12:41:44 +0200 Subject: [PATCH 27/81] chore(reactive-submit-button): implemented destroy --- frontend/src/utils/form/reactive-submit-button.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/frontend/src/utils/form/reactive-submit-button.js b/frontend/src/utils/form/reactive-submit-button.js index e46eed77e..c5bc3c642 100644 --- a/frontend/src/utils/form/reactive-submit-button.js +++ b/frontend/src/utils/form/reactive-submit-button.js @@ -1,4 +1,5 @@ import { Utility } from '../../core/utility'; +import { EventManager, EventWrapper, EVENT_TYPE } from '../../lib/event-manager/event-manager'; const REACTIVE_SUBMIT_BUTTON_INITIALIZED_CLASS = 'reactive-submit-button--initialized'; @@ -12,12 +13,15 @@ export class ReactiveSubmitButton { _requiredInputs; _submitButton; + _eventManager; + constructor(element) { if (!element) { throw new Error('Reactive Submit Button utility cannot be setup without an element!'); } this._element = element; + this._eventManager = new EventManager(); if (this._element.classList.contains(REACTIVE_SUBMIT_BUTTON_INITIALIZED_CLASS)) { return false; @@ -51,16 +55,18 @@ export class ReactiveSubmitButton { } destroy() { - // TODO + this._eventManager.removeAllEventListenersFromUtil(); + this._element.classList.remove(REACTIVE_SUBMIT_BUTTON_INITIALIZED_CLASS); } setupInputs() { this._requiredInputs.forEach((el) => { const checkbox = el.getAttribute('type') === 'checkbox'; - const eventType = checkbox ? 'change' : 'input'; - el.addEventListener(eventType, () => { + const eventType = checkbox ? EVENT_TYPE.CHANGE : EVENT_TYPE.INPUT; + const valEvent = new EventWrapper(eventType,(() => { this.updateButtonState(); - }); + }).bind(this), el ); + this._eventManager.registerNewListener(valEvent); }); } From abe84156d508dca8fce549b24d5902d24afc0dbf Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Fri, 16 Jul 2021 14:00:32 +0200 Subject: [PATCH 28/81] fix: prompt not shwowing up after submit/close --- frontend/src/utils/async-form/async-form.js | 2 +- .../src/utils/form/navigate-away-prompt.js | 12 ++++-- frontend/src/utils/modal/modal.js | 41 ++++++++++++++----- 3 files changed, 40 insertions(+), 15 deletions(-) diff --git a/frontend/src/utils/async-form/async-form.js b/frontend/src/utils/async-form/async-form.js index 19e147be8..17f2c0810 100644 --- a/frontend/src/utils/async-form/async-form.js +++ b/frontend/src/utils/async-form/async-form.js @@ -100,8 +100,8 @@ export class AsyncForm { ).catch(() => { const failureMessage = this._app.i18n.get('asyncFormFailure'); this._processResponse({ content: failureMessage }); - this._element.classList.remove(ASYNC_FORM_LOADING_CLASS); }); + this._app.utilRegistry.destroyAll(this._element); } } diff --git a/frontend/src/utils/form/navigate-away-prompt.js b/frontend/src/utils/form/navigate-away-prompt.js index 9c6a57eee..fd671bc81 100644 --- a/frontend/src/utils/form/navigate-away-prompt.js +++ b/frontend/src/utils/form/navigate-away-prompt.js @@ -27,18 +27,20 @@ const NAVIGATE_AWAY_PROMPT_UTIL_OPTOUT = '[uw-no-navigate-away-prompt]'; export class NavigateAwayPrompt { _element; + _app; _eventManager; _initFormData; _unloadDueToSubmit = false; - constructor(element) { + constructor(element, app) { if (!element) { throw new Error('Navigate Away Prompt utility needs to be passed an element!'); } this._element = element; + this._app = app; this._eventManager = new EventManager(); if (this._element.classList.contains(NAVIGATE_AWAY_PROMPT_INITIALIZED_CLASS)) { @@ -105,8 +107,12 @@ export class NavigateAwayPrompt { // allow the event to happen if the form was not touched by the // user (i.e. if the current FormData is equal to the initial FormData) // or the unload event was initiated by a form submit - if (!formDataHasChanged || this._unloadDueToSubmit) { - this.destroy(); //prevent propmt from triggering after the form was closed and the user navigates away + if (!formDataHasChanged) + return; + + + if(this._unloadDueToSubmit) { + this._app.utilRegistry.destroyAll(this._element); return; } diff --git a/frontend/src/utils/modal/modal.js b/frontend/src/utils/modal/modal.js index c67b13ac7..f45770d10 100644 --- a/frontend/src/utils/modal/modal.js +++ b/frontend/src/utils/modal/modal.js @@ -1,4 +1,5 @@ import { Utility } from '../../core/utility'; +import { EventManager, EventWrapper, EVENT_TYPE } from '../../lib/event-manager/event-manager'; import './modal.sass'; const MODAL_HEADERS = { @@ -28,12 +29,16 @@ const MODALS_WRAPPER_OPEN_CLASS = 'modals-wrapper--open'; }) export class Modal { + _eventManager + _element; _app; _modalsWrapper; _modalOverlay; _modalUrl; + _triggerElement; + _closerElement; constructor(element, app) { if (!element) { @@ -42,6 +47,7 @@ export class Modal { this._element = element; this._app = app; + this._eventManager = new EventManager(); if (this._element.classList.contains(MODAL_INITIALIZED_CLASS)) { return false; @@ -66,7 +72,16 @@ export class Modal { } destroy() { - // TODO + this._eventManager.cleanUp(); + if (this._closerElement !== undefined) + this._closerElement.remove(); + if(this._triggerElement !== undefined) + this._triggerElement.classList.remove(MODAL_TRIGGER_CLASS); + if(this._modalsWrapper !== undefined) + this._modalsWrapper.remove(); + if(this._modalOverlay !== undefined) + this._modalOverlay.remove(); + this._element.classList.remove(MODAL_INITIALIZED_CLASS, MODAL_CLASS); } _ensureModalWrapper() { @@ -92,23 +107,26 @@ export class Modal { if (!triggerSelector.startsWith('#')) { triggerSelector = '#' + triggerSelector; } - const triggerElement = document.querySelector(triggerSelector); + this._triggerElement = document.querySelector(triggerSelector); - if (!triggerElement) { + if (!this._triggerElement) { throw new Error('Trigger element for Modal not found: "' + triggerSelector + '"'); } - triggerElement.classList.add(MODAL_TRIGGER_CLASS); - triggerElement.addEventListener('click', this._onTriggerClicked, false); - this._modalUrl = triggerElement.getAttribute('href'); + this._triggerElement.classList.add(MODAL_TRIGGER_CLASS); + const triggerEvent = new EventWrapper(EVENT_TYPE.CLICK, this._onTriggerClicked.bind(this), this._triggerElement, false); + this._eventManager.registerNewListener(triggerEvent); + this._modalUrl = this._triggerElement.getAttribute('href'); } _setupCloser() { - const closerElement = document.createElement('div'); - this._element.insertBefore(closerElement, null); - closerElement.classList.add(MODAL_CLOSER_CLASS); - closerElement.addEventListener('click', this._onCloseClicked, false); - this._modalOverlay.addEventListener('click', this._onCloseClicked, false); + this._closerElement = document.createElement('div'); + this._element.insertBefore(this._closerElement, null); + this._closerElement.classList.add(MODAL_CLOSER_CLASS); + const closerElEvent = new EventWrapper(EVENT_TYPE.CLICK, this._onCloseClicked.bind(this), this._closerElement, false); + this._eventManager.registerNewListener(closerElEvent); + const overlayClose = new EventWrapper(EVENT_TYPE.CLICK, this._onCloseClicked.bind(this), this._modalOverlay, false); + this._eventManager.registerNewListener(overlayClose); } _onTriggerClicked = (event) => { @@ -119,6 +137,7 @@ export class Modal { _onCloseClicked = (event) => { event.preventDefault(); this._close(); + this._app.utilRegistry.destroyAll(this._element); } _onKeyUp = (event) => { From 9d5b8bc60ad6c692ccf68282f8add9640021cde4 Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Fri, 16 Jul 2021 14:57:42 +0200 Subject: [PATCH 29/81] chore(hide-columns): implemented destroy --- .../src/lib/event-manager/event-manager.js | 1 + .../src/utils/hide-columns/hide-columns.js | 43 +++++++++++++------ 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/frontend/src/lib/event-manager/event-manager.js b/frontend/src/lib/event-manager/event-manager.js index 35a5075e6..499ede4f9 100644 --- a/frontend/src/lib/event-manager/event-manager.js +++ b/frontend/src/lib/event-manager/event-manager.js @@ -5,6 +5,7 @@ export const EVENT_TYPE = { INVALID : 'invalid', CHANGE : 'change', MOUSE_OVER : 'mouseover', + MOUSE_OUT : 'mouseout', SUBMIT : 'submit', INPUT : 'input', FOCUS_OUT : 'focusout', diff --git a/frontend/src/utils/hide-columns/hide-columns.js b/frontend/src/utils/hide-columns/hide-columns.js index 7661ac92b..abfa37eb6 100644 --- a/frontend/src/utils/hide-columns/hide-columns.js +++ b/frontend/src/utils/hide-columns/hide-columns.js @@ -1,5 +1,7 @@ import { Utility } from '../../core/utility'; import { StorageManager, LOCATION } from '../../lib/storage-manager/storage-manager'; +import { EventManager, EventWrapper, EVENT_TYPE } from '../../lib/event-manager/event-manager'; + import './hide-columns.sass'; import { TableIndices } from '../../lib/table/table'; @@ -29,6 +31,7 @@ const HIDE_COLUMNS_INITIALIZED = 'uw-hide-columns--initialized'; export class HideColumns { _storageManager = new StorageManager('HIDE_COLUMNS', '1.1.0', { location: LOCATION.LOCAL }); + _eventManager; _element; _elementWrapper; @@ -36,8 +39,6 @@ export class HideColumns { _autoHide; - _mutationObserver; - _tableIndices; headerToHider = new Map(); @@ -62,6 +63,7 @@ export class HideColumns { return false; this._element = element; + this._eventManager = new EventManager(); this._tableIndices = new TableIndices(this._element); @@ -82,12 +84,18 @@ export class HideColumns { [...this._element.querySelectorAll('th')].filter(th => !th.hasAttribute(HIDE_COLUMNS_NO_HIDE)).forEach(th => this.setupHideButton(th)); - this._mutationObserver = new MutationObserver(this._tableMutated.bind(this)); - this._mutationObserver.observe(this._element, { childList: true, subtree: true }); + this._eventManager.registerNewMutationObserver(this._tableMutated.bind(this), this._element, { childList: true, subtree: true }); this._element.classList.add(HIDE_COLUMNS_INITIALIZED); } + destroy() { + this._eventManager.cleanUp(); + this._storageManager.clear(); + this._tableUtilContainer.remove(); + this._element.classList.remove(HIDE_COLUMNS_INITIALIZED); + } + setupHideButton(th) { const preHidden = this.isHiddenTH(th); @@ -104,34 +112,41 @@ export class HideColumns { this.addHeaderHider(th, hider); - th.addEventListener('mouseover', () => { + const mouseOverEvent = new EventWrapper(EVENT_TYPE.MOUSE_OVER, (() => { hider.classList.add(TABLE_HIDER_VISIBLE_CLASS); - }); - th.addEventListener('mouseout', () => { + }).bind(this), th); + this._eventManager.registerNewListener(mouseOverEvent); + + const mouseOutEvent = new EventWrapper(EVENT_TYPE.MOUSE_OUT, (() => { if (hider.classList.contains(TABLE_HIDER_CLASS)) { hider.classList.remove(TABLE_HIDER_VISIBLE_CLASS); } - }); + }).bind(this), th); + this._eventManager.registerNewListener(mouseOutEvent); - hider.addEventListener('click', (event) => { + const hideClickEvent = new EventWrapper(EVENT_TYPE.CLICK, ((event) => { event.preventDefault(); event.stopPropagation(); this.switchColumnDisplay(th); // this._tableHiderContainer.getElementsByClassName(TABLE_HIDER_CLASS).forEach(hider => this.hideHiderBehindHeader(hider)); - }); + }).bind(this)); + this._eventManager.registerNewListener(hideClickEvent); - hider.addEventListener('mouseover', () => { + const mouseOverHider = new EventWrapper(EVENT_TYPE.MOUSE_OVER, (() => { hider.classList.add(TABLE_HIDER_VISIBLE_CLASS); const currentlyHidden = this.hiderStatus(th); this.updateHiderIcon(hider, !currentlyHidden); - }); - hider.addEventListener('mouseout', () => { + }).bind(this), hider); + this._eventManager.registerNewListener(mouseOverHider); + + const mouseOutHider = new EventWrapper(EVENT_TYPE.MOUSE_OUT, (() => { if (hider.classList.contains(TABLE_HIDER_CLASS)) { hider.classList.remove(TABLE_HIDER_VISIBLE_CLASS); } const currentlyHidden = this.hiderStatus(th); this.updateHiderIcon(hider, currentlyHidden); - }); + }).bind(this), hider); + this._eventManger.registerNewListener(mouseOutHider); new ResizeObserver(() => { this.repositionHider(hider); }).observe(th); From 50a3ac1790f26c1e6f983d3687352d7811173110 Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Tue, 20 Jul 2021 11:05:15 +0200 Subject: [PATCH 30/81] fix(hide-colums): small fix --- frontend/src/utils/hide-columns/hide-columns.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/utils/hide-columns/hide-columns.js b/frontend/src/utils/hide-columns/hide-columns.js index abfa37eb6..bb2487c38 100644 --- a/frontend/src/utils/hide-columns/hide-columns.js +++ b/frontend/src/utils/hide-columns/hide-columns.js @@ -129,7 +129,7 @@ export class HideColumns { event.stopPropagation(); this.switchColumnDisplay(th); // this._tableHiderContainer.getElementsByClassName(TABLE_HIDER_CLASS).forEach(hider => this.hideHiderBehindHeader(hider)); - }).bind(this)); + }).bind(this), hider); this._eventManager.registerNewListener(hideClickEvent); const mouseOverHider = new EventWrapper(EVENT_TYPE.MOUSE_OVER, (() => { @@ -146,7 +146,7 @@ export class HideColumns { const currentlyHidden = this.hiderStatus(th); this.updateHiderIcon(hider, currentlyHidden); }).bind(this), hider); - this._eventManger.registerNewListener(mouseOutHider); + this._eventManager.registerNewListener(mouseOutHider); new ResizeObserver(() => { this.repositionHider(hider); }).observe(th); From dedbab689f1f9793b9cc3ebac160733d2f79e770 Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Tue, 20 Jul 2021 11:05:55 +0200 Subject: [PATCH 31/81] chore(checkbox): implemented destroy --- frontend/src/utils/inputs/checkbox.js | 34 +++++++++++++++++---------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/frontend/src/utils/inputs/checkbox.js b/frontend/src/utils/inputs/checkbox.js index 9b1b541ff..8c96d201d 100644 --- a/frontend/src/utils/inputs/checkbox.js +++ b/frontend/src/utils/inputs/checkbox.js @@ -9,43 +9,53 @@ const CHECKBOX_INITIALIZED_CLASS = 'checkbox--initialized'; selector: 'input[type="checkbox"]:not([uw-no-checkbox]), input[type="radio"]:not([uw-no-radiobox])', }) export class Checkbox { + + _element; + _wrapperEl; + constructor(element) { if (!element) { throw new Error('Checkbox utility cannot be setup without an element!'); } + this._element = element; - const isRadio = element.type === 'radio'; + const isRadio = this._element.type === 'radio'; const box_class = isRadio ? RADIOBOX_CLASS : CHECKBOX_CLASS; - if (isRadio && element.closest('.radio-group')) { + if (isRadio && this._element.closest('.radio-group')) { // Don't initialize radiobox, if radio is part of a group return false; } - if (element.classList.contains(CHECKBOX_INITIALIZED_CLASS)) { + if (this._element.classList.contains(CHECKBOX_INITIALIZED_CLASS)) { // throw new Error('Checkbox utility already initialized!'); return false; } - if (element.parentElement.classList.contains(box_class)) { + if (this._element.parentElement.classList.contains(box_class)) { // throw new Error('Checkbox element\'s wrapper already has class '' + box_class + ''!'); return false; } - const siblingEl = element.nextSibling; - const parentEl = element.parentElement; + const siblingEl = this._element.nextSibling; + const parentEl = this._element.parentElement; - const wrapperEl = document.createElement('div'); - wrapperEl.classList.add(box_class); + this._wrapperEl = document.createElement('div'); + this._wrapperEl.classList.add(box_class); const labelEl = document.createElement('label'); labelEl.setAttribute('for', element.id); - wrapperEl.appendChild(element); - wrapperEl.appendChild(labelEl); + this._wrapperEl.appendChild(element); + this._wrapperEl.appendChild(labelEl); - parentEl.insertBefore(wrapperEl, siblingEl); + parentEl.insertBefore(this._wrapperEl, siblingEl); - element.classList.add(CHECKBOX_INITIALIZED_CLASS); + this._element.classList.add(CHECKBOX_INITIALIZED_CLASS); + } + + destroy() { + this._wrapperEl.remove(); + this._element.classList.remove(CHECKBOX_INITIALIZED_CLASS); } } From 8cb3a41fcb96ce381a9c2e59001a7cedf67eb063 Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Tue, 20 Jul 2021 11:10:21 +0200 Subject: [PATCH 32/81] chore(file-input): implemented destroy --- frontend/src/utils/inputs/file-input.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/frontend/src/utils/inputs/file-input.js b/frontend/src/utils/inputs/file-input.js index e84d2ce26..6145a4970 100644 --- a/frontend/src/utils/inputs/file-input.js +++ b/frontend/src/utils/inputs/file-input.js @@ -1,4 +1,5 @@ import { Utility } from '../../core/utility'; +import { EventManager, EventWrapper, EVENT_TYPE } from '../../lib/event-manager/event-manager'; import './file-input.sass'; const FILE_INPUT_CLASS = 'file-input'; @@ -19,6 +20,8 @@ export class FileInput { _fileList; _label; + _eventManager; + constructor(element, app) { if (!element) { throw new Error('FileInput utility cannot be setup without an element!'); @@ -27,6 +30,8 @@ export class FileInput { this._element = element; this._app = app; + this._eventManager = new EventManager(); + if (this._element.classList.contains(FILE_INPUT_INITIALIZED_CLASS)) { throw new Error('FileInput utility already initialized!'); } @@ -40,11 +45,11 @@ export class FileInput { this._label = this._createFileLabel(); this._updateLabel(); - // add change listener - this._element.addEventListener('change', () => { + const changeInputEv = new EventWrapper(EVENT_TYPE.CHANGE,(() => { this._updateLabel(); this._renderFileList(); - }); + }).bind(this), this._element ); + this._eventManager.registerNewListener(changeInputEv); // add util class for styling and mark as initialized this._element.classList.add(FILE_INPUT_CLASS); @@ -52,7 +57,9 @@ export class FileInput { } destroy() { - // TODO + this._fileList.remove(); + this._label.remove(); + this._element.classList.remove(FILE_INPUT_INITIALIZED_CLASS); } _renderFileList() { From 1153ba8711c141c5b49ecf352d6a1ee2e6f3eb57 Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Tue, 20 Jul 2021 11:17:16 +0200 Subject: [PATCH 33/81] chore(file-max-size): implemented destroy --- frontend/src/utils/inputs/file-input.js | 1 + frontend/src/utils/inputs/file-max-size.js | 13 ++++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/frontend/src/utils/inputs/file-input.js b/frontend/src/utils/inputs/file-input.js index 6145a4970..300a15fa4 100644 --- a/frontend/src/utils/inputs/file-input.js +++ b/frontend/src/utils/inputs/file-input.js @@ -57,6 +57,7 @@ export class FileInput { } destroy() { + this._eventManager.cleanUp(); this._fileList.remove(); this._label.remove(); this._element.classList.remove(FILE_INPUT_INITIALIZED_CLASS); diff --git a/frontend/src/utils/inputs/file-max-size.js b/frontend/src/utils/inputs/file-max-size.js index 653cca287..d0b8e75f5 100644 --- a/frontend/src/utils/inputs/file-max-size.js +++ b/frontend/src/utils/inputs/file-max-size.js @@ -1,4 +1,5 @@ import { Utility } from '../../core/utility'; +import { EventManager, EventWrapper, EVENT_TYPE } from '../../lib/event-manager/event-manager'; const FILE_MAX_SIZE_INITIALIZED_CLASS = 'file-max-size--initialized'; @@ -9,6 +10,8 @@ export class FileMaxSize { _element; _app; + _eventManager; + constructor(element, app) { if (!element) throw new Error('FileMaxSize utility cannot be setup without an element!'); @@ -16,6 +19,8 @@ export class FileMaxSize { this._element = element; this._app = app; + this._eventManager = new EventManager(); + if (this._element.classList.contains(FILE_MAX_SIZE_INITIALIZED_CLASS)) { throw new Error('FileMaxSize utility already initialized!'); } @@ -24,7 +29,13 @@ export class FileMaxSize { } start() { - this._element.addEventListener('change', this._change.bind(this)); + const changeEv = new EventWrapper(EVENT_TYPE.CHANGE, this._change.bind(this), this._element); + this._eventManager.registerNewListener(changeEv); + } + + destroy() { + this._eventManager.cleanUp(); + this._element.classList.remove(FILE_MAX_SIZE_INITIALIZED_CLASS); } _change() { From b1c662da88df5853c3e047bbce4b4b6e93429154 Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Wed, 21 Jul 2021 10:54:45 +0200 Subject: [PATCH 34/81] chore(password): implemented destroy --- frontend/src/utils/inputs/password.js | 40 +++++++++++++++++++-------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/frontend/src/utils/inputs/password.js b/frontend/src/utils/inputs/password.js index 2bb750802..3f5fe3b8b 100644 --- a/frontend/src/utils/inputs/password.js +++ b/frontend/src/utils/inputs/password.js @@ -1,4 +1,5 @@ import { Utility } from '../../core/utility'; +import { EventManager, EventWrapper, EVENT_TYPE } from '../../lib/event-manager/event-manager'; const PASSWORD_INITIALIZED_CLASS = 'password-input--initialized'; @@ -9,6 +10,9 @@ export class Password { _element; _iconEl; _toggleContainerEl; + _wrapperEl; + + _eventManager; constructor(element) { if (!element) @@ -18,25 +22,26 @@ export class Password { return false; this._element = element; + this._eventManager = new EventManager(); this._element.classList.add('password-input__input'); const siblingEl = this._element.nextSibling; const parentEl = this._element.parentElement; - const wrapperEl = document.createElement('div'); - wrapperEl.classList.add('password-input__wrapper'); - wrapperEl.appendChild(this._element); + this._wrapperEl = document.createElement('div'); + this._wrapperEl.classList.add('password-input__wrapper'); + this._wrapperEl.appendChild(this._element); this._toggleContainerEl = document.createElement('div'); this._toggleContainerEl.classList.add('password-input__toggle'); - wrapperEl.appendChild(this._toggleContainerEl); + this._wrapperEl.appendChild(this._toggleContainerEl); this._iconEl = document.createElement('i'); this._iconEl.classList.add('fas', 'fa-fw'); this._toggleContainerEl.appendChild(this._iconEl); - parentEl.insertBefore(wrapperEl, siblingEl); + parentEl.insertBefore(this._wrapperEl, siblingEl); this._element.classList.add(PASSWORD_INITIALIZED_CLASS); } @@ -44,17 +49,30 @@ export class Password { start() { this.updateVisibleIcon(this.isVisible()); - this._toggleContainerEl.addEventListener('mouseover', () => { + const mouseOverEv = new EventWrapper(EVENT_TYPE.MOUS_OVER, (() => { this.updateVisibleIcon(!this.isVisible()); - }); - this._toggleContainerEl.addEventListener('mouseout', () => { + }).bind(this), this._toggleContainerEl); + + const mouseOutEv = new EventWrapper(EVENT_TYPE.MOUSE_OUT, (() => { this.updateVisibleIcon(this.isVisible()); - }); - this._toggleContainerEl.addEventListener('click', (event) => { + }).bind(this), this._toggleContainerEl); + + const clickEv = new EventWrapper(EVENT_TYPE.CLICK,((event) => { event.preventDefault(); event.stopPropagation(); this.setVisible(!this.isVisible()); - }); + }).bind(this), this._toggleContainerEl ); + + this._eventManager.registerListeners([mouseOverEv, mouseOutEv, clickEv]); + } + + destroy() { + this._iconEl.remove(); + this._toggleContainerEl.remove(); + this._wrapperEl.remove(); + this._iconEl.remove(); + + this._element.classList.remove(PASSWORD_INITIALIZED_CLASS); } isVisible() { From da8894a708b1c3b43d7462a27750c96431006fcc Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Wed, 21 Jul 2021 12:15:56 +0200 Subject: [PATCH 35/81] chore(radio): implemented destroy --- frontend/src/utils/inputs/radio.js | 38 ++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/frontend/src/utils/inputs/radio.js b/frontend/src/utils/inputs/radio.js index 38a3f0f2f..311f3fb53 100644 --- a/frontend/src/utils/inputs/radio.js +++ b/frontend/src/utils/inputs/radio.js @@ -9,39 +9,51 @@ const RADIO_INITIALIZED_CLASS = 'radio--initialized'; }) export class Radio { + _element; + _wrapperEl; + _labelEl; + constructor(element) { if (!element) { throw new Error('Radio utility cannot be setup without an element!'); } - if (element.closest('.radio-group')) { + this._element = element; + + if (this._element.closest('.radio-group')) { return false; } - if (element.classList.contains(RADIO_INITIALIZED_CLASS)) { + if (this._element.classList.contains(RADIO_INITIALIZED_CLASS)) { // throw new Error('Radio utility already initialized!'); return false; } - if (element.parentElement.classList.contains(RADIO_CLASS)) { + if (this._element.parentElement.classList.contains(RADIO_CLASS)) { // throw new Error('Radio element\'s wrapper already has class '' + RADIO_CLASS + ''!'); return false; } - const siblingEl = element.nextSibling; - const parentEl = element.parentElement; + const siblingEl = this._element.nextSibling; + const parentEl = this._element.parentElement; - const wrapperEl = document.createElement('div'); - wrapperEl.classList.add(RADIO_CLASS); + this._wrapperEl = document.createElement('div'); + this._wrapperEl.classList.add(RADIO_CLASS); - const labelEl = document.createElement('label'); - labelEl.setAttribute('for', element.id); + this._labelEl = document.createElement('label'); + this._labelEl.setAttribute('for', this._element.id); - wrapperEl.appendChild(element); - wrapperEl.appendChild(labelEl); + this._wrapperEl.appendChild(this._element); + this._wrapperEl.appendChild(this._labelEl); - parentEl.insertBefore(wrapperEl, siblingEl); + parentEl.insertBefore(this._wrapperEl, siblingEl); - element.classList.add(RADIO_INITIALIZED_CLASS); + this._element.classList.add(RADIO_INITIALIZED_CLASS); + } + + destroy() { + this._labelEl.remove(); + this._wrapperEl.remove(); + this._element.classList.remove(RADIO_INITIALIZED_CLASS); } } From 1a8fb230441f73b9b1fe593f4df1005a06d9628e Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Wed, 21 Jul 2021 12:17:41 +0200 Subject: [PATCH 36/81] feat(event-manager): added method to register a list of listeners --- frontend/src/lib/event-manager/event-manager.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/src/lib/event-manager/event-manager.js b/frontend/src/lib/event-manager/event-manager.js index 499ede4f9..03a798308 100644 --- a/frontend/src/lib/event-manager/event-manager.js +++ b/frontend/src/lib/event-manager/event-manager.js @@ -31,6 +31,10 @@ export class EventManager { this._registeredListeners.push(eventWrapper); } + registerListeners(eventWrappers) { + eventWrappers.forEach((eventWrapper) => this.registerNewListener(eventWrapper)); + } + registerNewMutationObserver(callback, domNode, config) { let observer = new MutationObserver(callback); observer.observe(domNode, config); From 1f978e65a82a91fb728a7ee2970a4fd9e6beb521 Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Wed, 21 Jul 2021 12:30:23 +0200 Subject: [PATCH 37/81] fix: smaller fixes and typos --- frontend/src/utils/check-all/check-all.js | 5 +++-- frontend/src/utils/inputs/checkbox.js | 3 ++- frontend/src/utils/inputs/password.js | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/frontend/src/utils/check-all/check-all.js b/frontend/src/utils/check-all/check-all.js index df151bc62..7d051cddd 100644 --- a/frontend/src/utils/check-all/check-all.js +++ b/frontend/src/utils/check-all/check-all.js @@ -41,7 +41,7 @@ export class CheckAll { if (DEBUG_MODE > 0) console.log(this._columns); - this._findCheckboxColumns().forEach(columnId => this._checkAllColumns.push(new CheckAllColumn(this._element, app, this._columns[columnId]))); + this._findCheckboxColumns().forEach(columnId => this._checkAllColumns.push(new CheckAllColumn(this._element, app, this._columns[columnId], this._eventManager))); // mark initialized this._element.classList.add(CHECK_ALL_INITIALIZED_CLASS); @@ -50,7 +50,8 @@ export class CheckAll { destroy() { this._eventManager.removeAllEventListenersFromUtil(); this._checkAllColumns.forEach((column) => { - column._checkAllCheckBox.remove(); + if (column._checkAllCheckBox !== undefined) + column._checkAllCheckBox.remove(); }); if(this._element.classList.contains(CHECK_ALL_INITIALIZED_CLASS)) diff --git a/frontend/src/utils/inputs/checkbox.js b/frontend/src/utils/inputs/checkbox.js index 8c96d201d..ebf721667 100644 --- a/frontend/src/utils/inputs/checkbox.js +++ b/frontend/src/utils/inputs/checkbox.js @@ -55,7 +55,8 @@ export class Checkbox { } destroy() { - this._wrapperEl.remove(); + if (this._wrapperEl !== undefined) + this._wrapperEl.remove(); this._element.classList.remove(CHECKBOX_INITIALIZED_CLASS); } } diff --git a/frontend/src/utils/inputs/password.js b/frontend/src/utils/inputs/password.js index 3f5fe3b8b..3793598ee 100644 --- a/frontend/src/utils/inputs/password.js +++ b/frontend/src/utils/inputs/password.js @@ -49,7 +49,7 @@ export class Password { start() { this.updateVisibleIcon(this.isVisible()); - const mouseOverEv = new EventWrapper(EVENT_TYPE.MOUS_OVER, (() => { + const mouseOverEv = new EventWrapper(EVENT_TYPE.MOUSE_OVER, (() => { this.updateVisibleIcon(!this.isVisible()); }).bind(this), this._toggleContainerEl); @@ -57,7 +57,7 @@ export class Password { this.updateVisibleIcon(this.isVisible()); }).bind(this), this._toggleContainerEl); - const clickEv = new EventWrapper(EVENT_TYPE.CLICK,((event) => { + const clickEv = new EventWrapper(EVENT_TYPE.CLICK, ((event) => { event.preventDefault(); event.stopPropagation(); this.setVisible(!this.isVisible()); From 74bb9fb548dfbb30ab367267acfec654c55f242d Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Mon, 26 Jul 2021 11:36:28 +0200 Subject: [PATCH 38/81] chore(mass-input): implemented destroy --- frontend/src/utils/mass-input/mass-input.js | 25 ++++++++++++--------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/frontend/src/utils/mass-input/mass-input.js b/frontend/src/utils/mass-input/mass-input.js index aaa5f7d0c..184706b4d 100644 --- a/frontend/src/utils/mass-input/mass-input.js +++ b/frontend/src/utils/mass-input/mass-input.js @@ -2,6 +2,7 @@ import { Utility } from '../../core/utility'; import { Datepicker } from '../form/datepicker'; +import { EventManager, EventWrapper, EVENT_TYPE } from '../../lib/event-manager/event-manager'; import './mass-input.sass'; const MASS_INPUT_CELL_SELECTOR = '.massinput__cell'; @@ -29,6 +30,8 @@ export class MassInput { _changedAdd = new Array(); + _eventManager; + constructor(element, app) { if (!element) { throw new Error('Mass Input utility cannot be setup without an element!'); @@ -37,6 +40,8 @@ export class MassInput { this._element = element; this._app = app; + this._eventManager = new EventManager(); + if (global !== undefined) this._global = global; else if (window !== undefined) @@ -64,9 +69,10 @@ export class MassInput { buttons.forEach((button) => { this._setupSubmitButton(button); }); - - this._massInputForm.addEventListener('submit', this._massInputFormSubmitHandler.bind(this)); - this._massInputForm.addEventListener('keypress', this._keypressHandler.bind(this)); + + const submitEv = new EventWrapper(EVENT_TYPE.SUBMIT, this._massInputFormSubmitHandler.bind(this), this._massInputForm); + const keyPressEv = new EventWrapper(EVENT_TYPE.KEYDOWN, this._keypressHandler.bind(this), this.massInputForm); + this._eventManager.registerListeners([submitEv, keyPressEv]); Array.from(this._element.querySelectorAll(MASS_INPUT_ADD_CELL_SELECTOR)).forEach(this._setupChangedHandlers.bind(this)); @@ -76,14 +82,16 @@ export class MassInput { destroy() { this._reset(); + this._eventManager.cleanUp(); + this._element.classList.remove(MASS_INPUT_INITIALIZED_CLASS); } _setupChangedHandlers(addCell) { Array.from(addCell.querySelectorAll(MASS_INPUT_ADD_CHANGE_FIELD_SELECTOR)).forEach(inputElem => { if (inputElem.closest('[uw-mass-input]') !== this._element) return; - - inputElem.addEventListener('change', () => { this._changedAdd.push(addCell); }); + const changeEv = new EventWrapper(EVENT_TYPE.CHANGE, (() => { this._changedAdd.push(addCell); }).bind(this), inputElem); + this._eventManager.registerNewListener(changeEv); }); } @@ -207,13 +215,13 @@ export class MassInput { _setupSubmitButton(button) { button.setAttribute('type', 'button'); button.classList.add(MASS_INPUT_SUBMIT_BUTTON_CLASS); - button.addEventListener('click', this._massInputFormSubmitHandler); + const buttonClickEv = new EventWrapper(EVENT_TYPE.CLICK, this._massInputFormSubmitHandler.bind(this), button); + this._eventManager.registerNewListener(buttonClickEv); } _resetSubmitButton(button) { button.setAttribute('type', 'submit'); button.classList.remove(MASS_INPUT_SUBMIT_BUTTON_CLASS); - button.removeEventListener('click', this._massInputFormSubmitHandler); } _processResponse(responseElement) { @@ -268,9 +276,6 @@ export class MassInput { } _reset() { - this._element.classList.remove(MASS_INPUT_INITIALIZED_CLASS); - this._massInputForm.removeEventListener('submit', this._massInputFormSubmitHandler); - this._massInputForm.removeEventListener('keypress', this._keypressHandler); const buttons = this._getMassInputSubmitButtons(); buttons.forEach((button) => { From 03b6e199f1add7805202d2b3e7bbc5a7811d8e8c Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Mon, 26 Jul 2021 18:11:28 +0200 Subject: [PATCH 39/81] chore(navbar): implemented destroy --- frontend/src/utils/navbar/navbar.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/frontend/src/utils/navbar/navbar.js b/frontend/src/utils/navbar/navbar.js index f31ba77bd..08c11428c 100644 --- a/frontend/src/utils/navbar/navbar.js +++ b/frontend/src/utils/navbar/navbar.js @@ -1,4 +1,5 @@ import { Utility } from '../../core/utility'; +import { EventManager, EventWrapper, EVENT_TYPE } from '../../lib/event-manager/event-manager'; import './navbar.sass'; import * as throttle from 'lodash.throttle'; @@ -18,6 +19,8 @@ export class NavHeaderContainerUtil { _throttleUpdateWasOpen; + _eventManager; + constructor(element) { if (!element) { throw new Error('Navbar Header Container utility needs to be passed an element!'); @@ -29,6 +32,9 @@ export class NavHeaderContainerUtil { this._element = element; this.radioButton = document.getElementById(`${this._element.id}-radio`); + + this._eventManager = new EventManager(); + if (!this.radioButton) { throw new Error('Navbar Header Container utility could not find associated radio button!'); } @@ -58,8 +64,9 @@ export class NavHeaderContainerUtil { if (!this.container) return; - window.addEventListener('click', this.clickHandler.bind(this)); - this.radioButton.addEventListener('change', this.throttleUpdateWasOpen.bind(this)); + const clickEv = new EventWrapper(EVENT_TYPE.CLICK, this.clickHandler.bind(this), window); + const changeEv = new EventWrapper(EVENT_TYPE.CHANGE, this.throttleUpdateWasOpen.bind(this), this.radioButton); + this._eventManager.registerListeners([clickEv, changeEv]); } clickHandler() { @@ -81,7 +88,10 @@ export class NavHeaderContainerUtil { this.wasOpen = this.isOpen(); } - destroy() { /* TODO */ } + destroy() { + this._eventManager.cleanUp(); + this._element.classList.remove(HEADER_CONTAINER_INITIALIZED_CLASS); + } } From d18f822ca5f622857b153d7ba35c2788765c7886 Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Mon, 26 Jul 2021 18:20:46 +0200 Subject: [PATCH 40/81] chore(show-hide): implemented destroy --- .../src/lib/event-manager/event-manager.js | 1 + frontend/src/utils/show-hide/show-hide.js | 20 ++++++++++++------- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/frontend/src/lib/event-manager/event-manager.js b/frontend/src/lib/event-manager/event-manager.js index 03a798308..edc97c91f 100644 --- a/frontend/src/lib/event-manager/event-manager.js +++ b/frontend/src/lib/event-manager/event-manager.js @@ -10,6 +10,7 @@ export const EVENT_TYPE = { INPUT : 'input', FOCUS_OUT : 'focusout', BEFOREUNLOAD : 'beforeunload', + HASH_CHANGE : 'hashchange', }; diff --git a/frontend/src/utils/show-hide/show-hide.js b/frontend/src/utils/show-hide/show-hide.js index 3419ee1f4..e7f7c3315 100644 --- a/frontend/src/utils/show-hide/show-hide.js +++ b/frontend/src/utils/show-hide/show-hide.js @@ -1,5 +1,6 @@ import { Utility } from '../../core/utility'; import { StorageManager, LOCATION } from '../../lib/storage-manager/storage-manager'; +import { EventManager, EventWrapper, EVENT_TYPE } from '../../lib/event-manager/event-manager'; import './show-hide.sass'; const SHOW_HIDE_LOCAL_STORAGE_KEY = 'SHOW_HIDE'; @@ -16,6 +17,7 @@ export class ShowHide { _showHideId; _element; + _eventManager; _storageManager = new StorageManager(SHOW_HIDE_LOCAL_STORAGE_KEY, '1.0.0', { location: LOCATION.LOCAL }); constructor(element) { @@ -24,13 +26,15 @@ export class ShowHide { } this._element = element; + this._eventManager = new EventManager(); if (this._element.classList.contains(SHOW_HIDE_INITIALIZED_CLASS)) { return false; } // register click listener - this._addClickListener(); + const clickEv = new EventWrapper(EVENT_TYPE.CLICK, this._clickHandler.bind(this), this._element); + this._eventManager.registerNewListener(clickEv); // param showHideId if (this._element.dataset.showHideId) { @@ -58,17 +62,19 @@ export class ShowHide { } this._checkHash(); - - window.addEventListener('hashchange', this._checkHash.bind(this)); + const hashChangeEv = new EventWrapper(EVENT_TYPE.HASH_CHANGE, this._checkHash.bind(this), window); + this._eventManager.registerNewListener(hashChangeEv); // mark as initialized this._element.classList.add(SHOW_HIDE_INITIALIZED_CLASS, SHOW_HIDE_TOGGLE_CLASS); } - destroy() {} - - _addClickListener() { - this._element.addEventListener('click', this._clickHandler.bind(this)); + destroy() { + this._eventManager.cleanUp(); + this._storageManager.clear(); + if(this._element.parentElement.contains(SHOW_HIDE_COLLAPSED_CLASS)) + this._element.parentElement.classList.remove(SHOW_HIDE_COLLAPSED_CLASS); + this._element.classList.remove(SHOW_HIDE_INITIALIZED_CLASS, SHOW_HIDE_TOGGLE_CLASS); } _show() { From 2796c940f0b3762f03f9cad9562c2847da333c85 Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Mon, 26 Jul 2021 18:43:03 +0200 Subject: [PATCH 41/81] chore(pageactions): implemented destroy --- frontend/src/utils/pageactions/pageactions.js | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/frontend/src/utils/pageactions/pageactions.js b/frontend/src/utils/pageactions/pageactions.js index 7c2334b6e..870065a4e 100644 --- a/frontend/src/utils/pageactions/pageactions.js +++ b/frontend/src/utils/pageactions/pageactions.js @@ -1,4 +1,5 @@ import { Utility } from '../../core/utility'; +import { EventManager, EventWrapper, EVENT_TYPE } from '../../lib/event-manager/event-manager'; import './pageactions.sass'; import * as throttle from 'lodash.throttle'; @@ -17,9 +18,12 @@ export class PageActionSecondaryUtil { closeButton; container; wasOpen; + _closer; _throttleUpdateWasOpen; + _eventManager; + constructor(element) { if (!element) { throw new Error('Pageaction Secondary utility needs to be passed an element!'); @@ -31,6 +35,8 @@ export class PageActionSecondaryUtil { this._element = element; + this._eventManager = new EventManager(); + const childContainer = this._element.querySelector('.pagenav-item__children'); if (!childContainer) { @@ -43,7 +49,7 @@ export class PageActionSecondaryUtil { const links = Array.from(this._element.querySelectorAll('.pagenav-item__link')).filter(l => !childContainer.contains(l)); if (!links || Array.from(links).length !== 1) { - throw new Error('Pageaction Secondary utility could not find associated link!'); + throw new Error('Pageaction Secondary utility could not find associated link!'); } this.navIdent = links[0].id; } @@ -71,9 +77,9 @@ export class PageActionSecondaryUtil { throw new Error('Pageaction Secondary utility could not find associated container!'); } - const closer = this._element.querySelector('.pagenav-item__close-label'); - if (closer) { - closer.classList.add('pagenav-item__close-label--hidden'); + this._closer = this._element.querySelector('.pagenav-item__close-label'); + if (this._closer) { + this._closer.classList.add('pagenav-item__close-label--hidden'); } this.updateWasOpen(); @@ -85,12 +91,12 @@ export class PageActionSecondaryUtil { start() { if (!this.container) return; - - window.addEventListener('click', this.clickHandler.bind(this)); - this.radioButton.addEventListener('change', this.throttleUpdateWasOpen.bind(this)); + const windowClickEv = new EventWrapper(EVENT_TYPE.CLICK, ((event) => this.clickHandler(event)).bind(this), window); + const radioButtonChangeEv = new EventWrapper(EVENT_TYPE.CHANGE, this.throttleUpdateWasOpen.bind(this), this.radioButton); + this._eventManager.registerListeners([windowClickEv, radioButtonChangeEv]); } - clickHandler() { + clickHandler(event) { if (!this.container.contains(event.target) && window.document.contains(event.target) && this.wasOpen) { this.close(); } @@ -109,7 +115,12 @@ export class PageActionSecondaryUtil { this.wasOpen = this.isOpen(); } - destroy() { /* TODO */ } + destroy() { + this._eventManager.cleanUp(); + if(this._closer && this._closer.classList.contains()) + this._closer.classList.remove('pagenav-item__close-label--hidden'); + this._element.classList.remove(PAGEACTION_SECONDARY_INITIALIZED_CLASS); + } } From 4156db3abba1bb10608c50f7ae1c19de7f410da0 Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Mon, 26 Jul 2021 18:47:18 +0200 Subject: [PATCH 42/81] chore(sort-table): implemented destroy --- frontend/src/utils/sort-table/sort-table.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/utils/sort-table/sort-table.js b/frontend/src/utils/sort-table/sort-table.js index 3c43a9ee2..639664685 100644 --- a/frontend/src/utils/sort-table/sort-table.js +++ b/frontend/src/utils/sort-table/sort-table.js @@ -21,7 +21,7 @@ export class SortTable { } destroy() { - console.log('TBD destroy SortTable'); + this._storageManager.clear(); } } From 01d9ce39805ec548f46c205c156a310f2a01359c Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Mon, 26 Jul 2021 19:24:22 +0200 Subject: [PATCH 43/81] chore(tooltips): implemented destroy --- frontend/src/utils/tooltips/tooltips.js | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/frontend/src/utils/tooltips/tooltips.js b/frontend/src/utils/tooltips/tooltips.js index 12d628f35..c520af204 100644 --- a/frontend/src/utils/tooltips/tooltips.js +++ b/frontend/src/utils/tooltips/tooltips.js @@ -1,6 +1,7 @@ import { Utility } from '../../core/utility'; import './tooltips.sass'; import { MovementObserver } from '../../lib/movement-observer/movement-observer'; +import { EventManager, EventWrapper, EVENT_TYPE } from '../../lib/event-manager/event-manager'; const TOOLTIP_CLASS = 'tooltip'; const TOOLTIP_INITIALIZED_CLASS = 'tooltip--initialized'; @@ -17,6 +18,7 @@ export class Tooltip { _content; _movementObserver; + _eventManager; _openedPersistent = false; @@ -45,16 +47,19 @@ export class Tooltip { this._element = element; this._handle = element.querySelector('.tooltip__handle') || element; + this._eventManager = new EventManager(); + this._movementObserver = new MovementObserver(this._handle, { leadingCallback: this.close.bind(this) }); element.classList.add(TOOLTIP_INITIALIZED_CLASS); } start() { - this._element.addEventListener('mouseover', () => { this.open(false); }); - this._element.addEventListener('mouseout', this._leave.bind(this)); - this._content.addEventListener('mouseout', this._leave.bind(this)); - this._element.addEventListener('click', this._click.bind(this)); + const mouseOverEv = new EventWrapper(EVENT_TYPE.MOUSE_OVER, (() => { this.open(false); }).bind(this), this._element); + const mouseOutEv = new EventWrapper(EVENT_TYPE.MOUSE_OUT, (this._leave.bind(this)).bind(this), this._element); + const contentMouseOut = new EventWrapper(EVENT_TYPE.MOUSE_OUT, (this._leave.bind(this)).bind(this), this._content); + const clickEv = new EventWrapper(EVENT_TYPE.CLICK, this._click.bind(this), this._element); + this.registerListeners([mouseOverEv, mouseOutEv, contentMouseOut, clickEv]); } open(persistent) { @@ -183,5 +188,15 @@ export class Tooltip { } - destroy() {} + destroy() { + this._eventManager.cleanUp(); + if(this._element.classList.contains(TOOLTIP_OPEN_CLASS)) + this._element.classList.remove(TOOLTIP_OPEN_CLASS); + if(this._element.classList.contains('tooltip--right')) + this._element.classList.remove('tooltip--right'); + if(this._element.classList.contains('tooltip--bottom')) + this._element.classList.remove('tooltip--bottom'); + this._movementObserver.unobserve(); + this._element.classList.remove(TOOLTIP_INITIALIZED_CLASS); + } }; From 6320cd927a84445f056dc782fb440d276cb26009 Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Thu, 29 Jul 2021 16:51:40 +0200 Subject: [PATCH 44/81] fix: fixed a few minor issues --- frontend/src/services/util-registry/util-registry.js | 6 +++--- frontend/src/utils/alerts/alerts.js | 6 +++--- frontend/src/utils/mass-input/mass-input.js | 2 +- frontend/src/utils/pageactions/pageactions.js | 2 +- frontend/src/utils/show-hide/show-hide.js | 4 ++-- frontend/src/utils/tooltips/tooltips.js | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/frontend/src/services/util-registry/util-registry.js b/frontend/src/services/util-registry/util-registry.js index b8dea6ec3..67cc09702 100644 --- a/frontend/src/services/util-registry/util-registry.js +++ b/frontend/src/services/util-registry/util-registry.js @@ -101,9 +101,9 @@ export class UtilRegistry { let utilsInScope = this._getUtilInstancesWithinScope(scope); utilsInScope.forEach((util) => { - //if(DEBUG_MODE > 2) { - console.log('Destroying Util: ', {util}); - //}# + if(DEBUG_MODE > 2) { + console.log('Destroying Util: ', {util}); + } let utilIndex = this._activeUtilInstancesWrapped.indexOf(util); util.destroy(); this._activeUtilInstancesWrapped.splice(utilIndex, 1); diff --git a/frontend/src/utils/alerts/alerts.js b/frontend/src/utils/alerts/alerts.js index 4f84d8aac..8f7877e68 100644 --- a/frontend/src/utils/alerts/alerts.js +++ b/frontend/src/utils/alerts/alerts.js @@ -103,9 +103,9 @@ export class Alerts { } const closeEl = alertElement.querySelector('.' + ALERT_CLOSER_CLASS); - const closeAlertEvent = new EventWrapper(EVENT_TYPE.CLICK, () => { - this._toggleAlert(alertElement).bind(this); - }, closeEl); + const closeAlertEvent = new EventWrapper(EVENT_TYPE.CLICK, (() => { + this._toggleAlert(alertElement); + }).bind(this), closeEl); this._eventManager.registerNewListener(closeAlertEvent); diff --git a/frontend/src/utils/mass-input/mass-input.js b/frontend/src/utils/mass-input/mass-input.js index 184706b4d..4969e8c14 100644 --- a/frontend/src/utils/mass-input/mass-input.js +++ b/frontend/src/utils/mass-input/mass-input.js @@ -71,7 +71,7 @@ export class MassInput { }); const submitEv = new EventWrapper(EVENT_TYPE.SUBMIT, this._massInputFormSubmitHandler.bind(this), this._massInputForm); - const keyPressEv = new EventWrapper(EVENT_TYPE.KEYDOWN, this._keypressHandler.bind(this), this.massInputForm); + const keyPressEv = new EventWrapper(EVENT_TYPE.KEYDOWN, this._keypressHandler.bind(this), this._massInputForm); this._eventManager.registerListeners([submitEv, keyPressEv]); Array.from(this._element.querySelectorAll(MASS_INPUT_ADD_CELL_SELECTOR)).forEach(this._setupChangedHandlers.bind(this)); diff --git a/frontend/src/utils/pageactions/pageactions.js b/frontend/src/utils/pageactions/pageactions.js index 870065a4e..88084636b 100644 --- a/frontend/src/utils/pageactions/pageactions.js +++ b/frontend/src/utils/pageactions/pageactions.js @@ -117,7 +117,7 @@ export class PageActionSecondaryUtil { destroy() { this._eventManager.cleanUp(); - if(this._closer && this._closer.classList.contains()) + if(this._closer && this._closer.classList.contains('pagenav-item__close-label--hidden')) this._closer.classList.remove('pagenav-item__close-label--hidden'); this._element.classList.remove(PAGEACTION_SECONDARY_INITIALIZED_CLASS); } diff --git a/frontend/src/utils/show-hide/show-hide.js b/frontend/src/utils/show-hide/show-hide.js index e7f7c3315..411cf1a7d 100644 --- a/frontend/src/utils/show-hide/show-hide.js +++ b/frontend/src/utils/show-hide/show-hide.js @@ -70,9 +70,9 @@ export class ShowHide { } destroy() { + this._storageManager.clear({ location: LOCATION.LOCAL }); this._eventManager.cleanUp(); - this._storageManager.clear(); - if(this._element.parentElement.contains(SHOW_HIDE_COLLAPSED_CLASS)) + if (this._element.parentElement.classList.contains(SHOW_HIDE_COLLAPSED_CLASS)) this._element.parentElement.classList.remove(SHOW_HIDE_COLLAPSED_CLASS); this._element.classList.remove(SHOW_HIDE_INITIALIZED_CLASS, SHOW_HIDE_TOGGLE_CLASS); } diff --git a/frontend/src/utils/tooltips/tooltips.js b/frontend/src/utils/tooltips/tooltips.js index c520af204..2a4d77e9b 100644 --- a/frontend/src/utils/tooltips/tooltips.js +++ b/frontend/src/utils/tooltips/tooltips.js @@ -59,7 +59,7 @@ export class Tooltip { const mouseOutEv = new EventWrapper(EVENT_TYPE.MOUSE_OUT, (this._leave.bind(this)).bind(this), this._element); const contentMouseOut = new EventWrapper(EVENT_TYPE.MOUSE_OUT, (this._leave.bind(this)).bind(this), this._content); const clickEv = new EventWrapper(EVENT_TYPE.CLICK, this._click.bind(this), this._element); - this.registerListeners([mouseOverEv, mouseOutEv, contentMouseOut, clickEv]); + this._eventManager.registerListeners([mouseOverEv, mouseOutEv, contentMouseOut, clickEv]); } open(persistent) { From e5290f0e57c48d9ce9235ef9ac884864aa45d163 Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Tue, 3 Aug 2021 12:00:45 +0200 Subject: [PATCH 45/81] chore(event-manger): uncommented debuglog --- frontend/src/lib/event-manager/event-manager.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/lib/event-manager/event-manager.js b/frontend/src/lib/event-manager/event-manager.js index edc97c91f..a69965a6f 100644 --- a/frontend/src/lib/event-manager/event-manager.js +++ b/frontend/src/lib/event-manager/event-manager.js @@ -62,11 +62,11 @@ export class EventManager { } - //Todo: Uncomment debug log! - //_debugLog() {} - _debugLog(fName, ...args) { - console.log(`[DEBUGLOG] EventManager.${fName}`, { args: args, instance: this }); - } + + _debugLog() {} + //_debugLog(fName, ...args) { + // console.log(`[DEBUGLOG] EventManager.${fName}`, { args: args, instance: this }); + //} } export class EventWrapper { From 8c818a46aba9475d55572f699d1541ac77bff2ac Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Tue, 3 Aug 2021 12:14:30 +0200 Subject: [PATCH 46/81] chore(alert): added a start method to alert util --- frontend/src/utils/alerts/alerts.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/src/utils/alerts/alerts.js b/frontend/src/utils/alerts/alerts.js index 8f7877e68..fd9681f89 100644 --- a/frontend/src/utils/alerts/alerts.js +++ b/frontend/src/utils/alerts/alerts.js @@ -52,25 +52,25 @@ export class Alerts { this._togglerElement = this._element.querySelector('.' + ALERTS_TOGGLER_CLASS); this._alertElements = this._gatherAlertElements(); + // mark initialized + this._element.classList.add(ALERTS_INITIALIZED_CLASS); + } + + start() { if (this._togglerElement) { - //should there be a start method, to initialize the listeners in initToggler and initAlerts or is this wanted? this._initToggler(); } - this._initAlerts(); // register http client interceptor to filter out Alerts Header this._setupHttpInterceptor(); - - // mark initialized - this._element.classList.add(ALERTS_INITIALIZED_CLASS); } destroy() { this._eventManager.removeAllEventListenersFromUtil(); if(this._alertElements) { - this._alertElements.forEach(element => element.remove() ); + this._alertElements.forEach(element => element.remove()); } if(this._element.classList.contains(ALERTS_INITIALIZED_CLASS)) From 0823df33b5f157af290aca9a65f1df429048e53b Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Tue, 3 Aug 2021 17:34:28 +0200 Subject: [PATCH 47/81] feat(http-client): added possibility to remove specific interceptors --- frontend/src/services/http-client/http-client.js | 12 ++++++++++++ .../src/services/http-client/http-client.spec.js | 10 +++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/frontend/src/services/http-client/http-client.js b/frontend/src/services/http-client/http-client.js index 274f86cdb..28d7cba21 100644 --- a/frontend/src/services/http-client/http-client.js +++ b/frontend/src/services/http-client/http-client.js @@ -15,6 +15,18 @@ export class HttpClient { } } + removeResponseInterceptor(interceptor) { + //performs a reference check. if the interceptor is bound, when adding it, the reference of the bound function needs to be the same when removing it later. + + if (typeof interceptor !== 'function') { + throw new Error(`Cannot remove Interceptor ${interceptor}, because it is not of type function`); + } + if(this._responseInterceptors.filter(el => el == interceptor).length !== 1) { + throw new Error(`Could not find Response Interceptor ${interceptor}.`); + } + this._responseInterceptors = this._responseInterceptors.filter(el => el != interceptor); + } + _baseUrl; setBaseUrl(baseUrl) { diff --git a/frontend/src/services/http-client/http-client.spec.js b/frontend/src/services/http-client/http-client.spec.js index a0f76584d..594f7096c 100644 --- a/frontend/src/services/http-client/http-client.spec.js +++ b/frontend/src/services/http-client/http-client.spec.js @@ -75,7 +75,7 @@ describe('HttpClient', () => { expect(httpClient._responseInterceptors.length).toBe(2); }); - describe('get called', () => { + describe('get called and removed', () => { let intercepted1; let intercepted2; const interceptors = { @@ -111,6 +111,14 @@ describe('HttpClient', () => { done(); }); }); + + it('can be removed', () => { + expect(httpClient._responseInterceptors.length).toBe(2); + httpClient.removeResponseInterceptor(interceptors.interceptor1); + expect(httpClient._responseInterceptors.length).toBe(1); + expect(() => {httpClient.removeResponseInterceptor(interceptors.interceptor1);}).toThrow(); + expect(httpClient._responseInterceptors.length).toBe(1); + }); }); }); }); From fc7d5dc94e7a2d9181c3c938c37ddaa74727553a Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Tue, 3 Aug 2021 17:35:10 +0200 Subject: [PATCH 48/81] chore(alert): removing the interceptor when destroying the util --- frontend/src/utils/alerts/alerts.js | 5 ++++- frontend/src/utils/alerts/alerts.spec.js | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/src/utils/alerts/alerts.js b/frontend/src/utils/alerts/alerts.js index fd9681f89..bf29445eb 100644 --- a/frontend/src/utils/alerts/alerts.js +++ b/frontend/src/utils/alerts/alerts.js @@ -34,6 +34,7 @@ export class Alerts { _app; _eventManager; + _boundResponseInterceptor; constructor(element, app) { if (!element) { @@ -44,6 +45,7 @@ export class Alerts { this._app = app; this._eventManager = new EventManager(); + this._boundResponseInterceptor = this._responseInterceptor.bind(this); if (this._element.classList.contains(ALERTS_INITIALIZED_CLASS)) { return false; @@ -68,6 +70,7 @@ export class Alerts { destroy() { this._eventManager.removeAllEventListenersFromUtil(); + this._app.httpClient.removeResponseInterceptor(this._boundResponseInterceptor); if(this._alertElements) { this._alertElements.forEach(element => element.remove()); @@ -135,7 +138,7 @@ export class Alerts { } _setupHttpInterceptor() { - this._app.httpClient.addResponseInterceptor(this._responseInterceptor.bind(this)); + this._app.httpClient.addResponseInterceptor(this._boundResponseInterceptor); } _elevateAlerts() { diff --git a/frontend/src/utils/alerts/alerts.spec.js b/frontend/src/utils/alerts/alerts.spec.js index 889229f7c..db14c7361 100644 --- a/frontend/src/utils/alerts/alerts.spec.js +++ b/frontend/src/utils/alerts/alerts.spec.js @@ -3,6 +3,7 @@ import { Alerts, ALERTS_INITIALIZED_CLASS } from './alerts'; const MOCK_APP = { httpClient: { addResponseInterceptor: () => {}, + removeResponseInterceptor: () => {}, }, }; From 14a16c7283483ff22ce22b070a430a61d24a7f35 Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Fri, 6 Aug 2021 13:50:32 +0200 Subject: [PATCH 49/81] fix(async-form): destroy all after response is processed --- frontend/src/utils/async-form/async-form.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/utils/async-form/async-form.js b/frontend/src/utils/async-form/async-form.js index 17f2c0810..1365150c6 100644 --- a/frontend/src/utils/async-form/async-form.js +++ b/frontend/src/utils/async-form/async-form.js @@ -60,6 +60,7 @@ export class AsyncForm { setTimeout(() => { parentElement.insertBefore(responseElement, this._element); this._element.remove(); + this._app.utilRegistry.destroyAll(this._element); }, delay); } @@ -102,6 +103,5 @@ export class AsyncForm { this._processResponse({ content: failureMessage }); this._element.classList.remove(ASYNC_FORM_LOADING_CLASS); }); - this._app.utilRegistry.destroyAll(this._element); } } From 9843cdf3c4b7634a10d121b0eb974df202ef7cab Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Fri, 6 Aug 2021 15:11:28 +0200 Subject: [PATCH 50/81] chore(async-table): added destroy before new html is set --- frontend/src/utils/async-table/async-table.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/utils/async-table/async-table.js b/frontend/src/utils/async-table/async-table.js index 68785f276..c0536136e 100644 --- a/frontend/src/utils/async-table/async-table.js +++ b/frontend/src/utils/async-table/async-table.js @@ -447,6 +447,8 @@ export class AsyncTable { this._active = false; this._element.classList.remove(ASYNC_TABLE_INITIALIZED_CLASS); this._element.dataset['currentTableUrl'] = url.href; + + this._app.utilRegistry.destroyAll(this._element); // update table with new this._element.innerHTML = response.element.innerHTML; From ad3bf94c20972db9a437dc9dcda201e86263a6e7 Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Fri, 6 Aug 2021 15:42:09 +0200 Subject: [PATCH 51/81] chore(async-table): mutation observer is handeled in event manager --- frontend/src/utils/async-table/async-table.js | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/frontend/src/utils/async-table/async-table.js b/frontend/src/utils/async-table/async-table.js index c0536136e..28c4abb00 100644 --- a/frontend/src/utils/async-table/async-table.js +++ b/frontend/src/utils/async-table/async-table.js @@ -240,17 +240,7 @@ export class AsyncTable { const debouncedUpdateFromTableFilter = throttle((() => this._updateFromTableFilter(tableFilterForm)).bind(this), FILTER_DEBOUNCE, { leading: true, trailing: false }); [...this._tableFilterInputs.search, ...this._tableFilterInputs.input].forEach((input) => { - const submitLockObserver = new MutationObserver((mutations, observer) => { - for (const mutation of mutations) { - // if the submit lock has been released, trigger an update and disconnect this observer - if (mutation.target === input && mutation.attributeName === ATTR_SUBMIT_LOCKED && mutation.oldValue === 'true' && mutation.target.getAttribute(mutation.attributeName) === 'false') { - debouncedUpdateFromTableFilter(); - observer.disconnect(); - break; - } - } - }); - this._cancelPendingUpdates.push(() => { submitLockObserver.disconnect(); }); + this._cancelPendingUpdates.push(() => { this._eventManager.removeAllObserversFromUtil();}); const debouncedInput = debounce(() => { const submitLockedAttr = input.getAttribute(ATTR_SUBMIT_LOCKED); @@ -259,7 +249,16 @@ export class AsyncTable { debouncedUpdateFromTableFilter(); } else if (submitLockedAttr === 'true') { // observe the submit lock of the input element - submitLockObserver.observe(input, { + this._eventManager.registerNewMutationObserver(((mutations, observer) => { + for (const mutation of mutations) { + // if the submit lock has been released, trigger an update and disconnect this observer + if (mutation.target === input && mutation.attributeName === ATTR_SUBMIT_LOCKED && mutation.oldValue === 'true' && mutation.target.getAttribute(mutation.attributeName) === 'false') { + debouncedUpdateFromTableFilter(); + observer.disconnect(); + break; + } + } + }).bind(this), input, { attributes: true, attributeFilter: [ATTR_SUBMIT_LOCKED], attributeOldValue: true, From 7a0715906c8336ed64bbfabdf614746ade9f661f Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Fri, 6 Aug 2021 15:50:46 +0200 Subject: [PATCH 52/81] fix(navigate-away-promp): removed unnecessary destroyAll --- frontend/src/utils/form/navigate-away-prompt.js | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/utils/form/navigate-away-prompt.js b/frontend/src/utils/form/navigate-away-prompt.js index fd671bc81..bb05238aa 100644 --- a/frontend/src/utils/form/navigate-away-prompt.js +++ b/frontend/src/utils/form/navigate-away-prompt.js @@ -112,7 +112,6 @@ export class NavigateAwayPrompt { if(this._unloadDueToSubmit) { - this._app.utilRegistry.destroyAll(this._element); return; } From 0688eef70ccd2fc1f72cf3d3124114948aaf2205 Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Fri, 6 Aug 2021 15:59:29 +0200 Subject: [PATCH 53/81] chore: cleanUp instead of removeListeners --- frontend/src/lib/event-manager/event-manager.js | 8 ++++---- frontend/src/utils/alerts/alerts.js | 2 +- frontend/src/utils/asidenav/asidenav.js | 2 +- frontend/src/utils/async-form/async-form.js | 2 +- frontend/src/utils/async-table/async-table.js | 2 +- frontend/src/utils/check-all/check-all.js | 2 +- frontend/src/utils/course-teaser/course-teaser.js | 2 +- frontend/src/utils/exam-correct/exam-correct.js | 2 +- frontend/src/utils/form/auto-submit-input.js | 2 +- frontend/src/utils/form/communication-recipients.js | 3 +-- frontend/src/utils/form/datepicker.js | 2 +- frontend/src/utils/form/enter-is-tab.js | 2 +- frontend/src/utils/form/form-error-remover.js | 2 +- frontend/src/utils/form/form-error-reporter.js | 2 +- 14 files changed, 17 insertions(+), 18 deletions(-) diff --git a/frontend/src/lib/event-manager/event-manager.js b/frontend/src/lib/event-manager/event-manager.js index a69965a6f..851e74eeb 100644 --- a/frontend/src/lib/event-manager/event-manager.js +++ b/frontend/src/lib/event-manager/event-manager.js @@ -63,10 +63,10 @@ export class EventManager { - _debugLog() {} - //_debugLog(fName, ...args) { - // console.log(`[DEBUGLOG] EventManager.${fName}`, { args: args, instance: this }); - //} + //_debugLog() {} + _debugLog(fName, ...args) { + console.log(`[DEBUGLOG] EventManager.${fName}`, { args: args, instance: this }); + } } export class EventWrapper { diff --git a/frontend/src/utils/alerts/alerts.js b/frontend/src/utils/alerts/alerts.js index bf29445eb..a88fd1f19 100644 --- a/frontend/src/utils/alerts/alerts.js +++ b/frontend/src/utils/alerts/alerts.js @@ -69,7 +69,7 @@ export class Alerts { } destroy() { - this._eventManager.removeAllEventListenersFromUtil(); + this._eventManager.cleanUp(); this._app.httpClient.removeResponseInterceptor(this._boundResponseInterceptor); if(this._alertElements) { diff --git a/frontend/src/utils/asidenav/asidenav.js b/frontend/src/utils/asidenav/asidenav.js index 560183e8c..324fc3fd8 100644 --- a/frontend/src/utils/asidenav/asidenav.js +++ b/frontend/src/utils/asidenav/asidenav.js @@ -40,7 +40,7 @@ export class Asidenav { destroy() { - this._eventManager.removeAllEventListenersFromUtil(); + this._eventManager.cleanUp(); if(this._element.classList.contains(ASIDENAV_INITIALIZED_CLASS)) this._element.classList.remove(ASIDENAV_INITIALIZED_CLASS); diff --git a/frontend/src/utils/async-form/async-form.js b/frontend/src/utils/async-form/async-form.js index 1365150c6..482cdb634 100644 --- a/frontend/src/utils/async-form/async-form.js +++ b/frontend/src/utils/async-form/async-form.js @@ -44,7 +44,7 @@ export class AsyncForm { } destroy() { - this._eventManager.removeAllEventListenersFromUtil(); + this._eventManager.cleanUp(); if(this._element.classList.contains(ASYNC_FORM_INITIALIZED_CLASS)) this._element.classList.remove(ASYNC_FORM_INITIALIZED_CLASS); diff --git a/frontend/src/utils/async-table/async-table.js b/frontend/src/utils/async-table/async-table.js index 28c4abb00..6ddf4536a 100644 --- a/frontend/src/utils/async-table/async-table.js +++ b/frontend/src/utils/async-table/async-table.js @@ -150,7 +150,7 @@ export class AsyncTable { destroy() { this._windowStorage.clear(this._windowStorage._options); - this._eventManager.removeAllEventListenersFromUtil(); + this._eventManager.cleanUp(); this._active = false; if (this._element.classList.contains(ASYNC_TABLE_INITIALIZED_CLASS)) this._element.classList.remove(ASYNC_TABLE_INITIALIZED_CLASS); diff --git a/frontend/src/utils/check-all/check-all.js b/frontend/src/utils/check-all/check-all.js index 7d051cddd..79558a75f 100644 --- a/frontend/src/utils/check-all/check-all.js +++ b/frontend/src/utils/check-all/check-all.js @@ -48,7 +48,7 @@ export class CheckAll { } destroy() { - this._eventManager.removeAllEventListenersFromUtil(); + this._eventManager.cleanUp(); this._checkAllColumns.forEach((column) => { if (column._checkAllCheckBox !== undefined) column._checkAllCheckBox.remove(); diff --git a/frontend/src/utils/course-teaser/course-teaser.js b/frontend/src/utils/course-teaser/course-teaser.js index 95a49faee..31d8cf225 100644 --- a/frontend/src/utils/course-teaser/course-teaser.js +++ b/frontend/src/utils/course-teaser/course-teaser.js @@ -30,7 +30,7 @@ export class CourseTeaser { } destroy() { - this._eventManager.removeAllEventListenersFromUtil(); + this._eventManager.cleanUp(); if(this._element.classList.contains(COURSE_TEASER_EXPANDED_CLASS)) { this._element.classList.remove(COURSE_TEASER_EXPANDED_CLASS); } diff --git a/frontend/src/utils/exam-correct/exam-correct.js b/frontend/src/utils/exam-correct/exam-correct.js index c88ce2d6b..f078f9825 100644 --- a/frontend/src/utils/exam-correct/exam-correct.js +++ b/frontend/src/utils/exam-correct/exam-correct.js @@ -172,7 +172,7 @@ export class ExamCorrect { } destroy() { - this._eventManager.removeAllEventListenersFromUtil(); + this._eventManager.cleanUp(); } _updatePartDeleteDisabled(deleteBox) { diff --git a/frontend/src/utils/form/auto-submit-input.js b/frontend/src/utils/form/auto-submit-input.js index 8ec7e869e..291a1d3aa 100644 --- a/frontend/src/utils/form/auto-submit-input.js +++ b/frontend/src/utils/form/auto-submit-input.js @@ -45,7 +45,7 @@ export class AutoSubmitInput { } destroy() { - this._eventManager.removeAllEventListenersFromUtil(); + this._eventManager.cleanUp(); if(this._element.classList.contains(AUTO_SUBMIT_INPUT_INITIALIZED_CLASS)) this._element.classList.remove(AUTO_SUBMIT_INPUT_INITIALIZED_CLASS); } diff --git a/frontend/src/utils/form/communication-recipients.js b/frontend/src/utils/form/communication-recipients.js index dc04f17cc..b6d754e09 100644 --- a/frontend/src/utils/form/communication-recipients.js +++ b/frontend/src/utils/form/communication-recipients.js @@ -44,8 +44,7 @@ export class CommunicationRecipients { } destroy() { - this._eventManager.removeAllEventListenersFromUtil(); - this._eventManager.removeAllObserversFromUtil(); + this._eventManager.cleanUp(); this._removeCheckedCounter(); } diff --git a/frontend/src/utils/form/datepicker.js b/frontend/src/utils/form/datepicker.js index 7a8abe3ad..c6273c07b 100644 --- a/frontend/src/utils/form/datepicker.js +++ b/frontend/src/utils/form/datepicker.js @@ -243,7 +243,7 @@ export class Datepicker { destroy() { this.datepickerInstance.remove(); - this._eventManager.removeAllListenersFromUtil(); + this._eventManager.cleanUp(); this._element.classList.remove(DATEPICKER_INITIALIZED_CLASS); } diff --git a/frontend/src/utils/form/enter-is-tab.js b/frontend/src/utils/form/enter-is-tab.js index 0a3073b9e..b3097d50d 100644 --- a/frontend/src/utils/form/enter-is-tab.js +++ b/frontend/src/utils/form/enter-is-tab.js @@ -56,7 +56,7 @@ export class EnterIsTab { } destroy() { - this._eventManager.removeAllEventListenersFromUtil(); + this._eventManager.cleanUp(); if(this._element.classList.contains(ENTER_IS_TAB_INITIALIZED_CLASS)) this._element.classList.remove(ENTER_IS_TAB_INITIALIZED_CLASS); } diff --git a/frontend/src/utils/form/form-error-remover.js b/frontend/src/utils/form/form-error-remover.js index 5fc1f90c0..f05e96fae 100644 --- a/frontend/src/utils/form/form-error-remover.js +++ b/frontend/src/utils/form/form-error-remover.js @@ -49,7 +49,7 @@ export class FormErrorRemover { } destroy() { - this._eventManager.removeAllEventListenersFromUtil(); + this._eventManager.cleanUp(); this._element.classList.remove(FORM_ERROR_REMOVER_INITIALIZED_CLASS); } diff --git a/frontend/src/utils/form/form-error-reporter.js b/frontend/src/utils/form/form-error-reporter.js index 8ebd14d5f..ed3012b26 100644 --- a/frontend/src/utils/form/form-error-reporter.js +++ b/frontend/src/utils/form/form-error-reporter.js @@ -38,7 +38,7 @@ export class FormErrorReporter { } destroy() { - this._eventManager.removeAllEventListenersFromUtil(); + this._eventManager.cleanUp(); this._removeError(); From 204ce39f7c2f9c405971aa59da068a5389dda417 Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Fri, 6 Aug 2021 18:39:40 +0200 Subject: [PATCH 54/81] fix(password): added cleanUP --- frontend/src/utils/async-table/async-table.js | 8 ++++---- frontend/src/utils/inputs/password.js | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/frontend/src/utils/async-table/async-table.js b/frontend/src/utils/async-table/async-table.js index 6ddf4536a..1734de1b1 100644 --- a/frontend/src/utils/async-table/async-table.js +++ b/frontend/src/utils/async-table/async-table.js @@ -462,10 +462,10 @@ export class AsyncTable { ).finally(() => this._element.classList.remove(ASYNC_TABLE_LOADING_CLASS)); } - //_debugLog() {} - _debugLog(fName, ...args) { - console.log(`[DEBUGLOG] AsyncTable.${fName}`, { args: args, instance: this }); - } + _debugLog() {} + //_debugLog(fName, ...args) { + // console.log(`[DEBUGLOG] AsyncTable.${fName}`, { args: args, instance: this }); + // } } diff --git a/frontend/src/utils/inputs/password.js b/frontend/src/utils/inputs/password.js index 3793598ee..0659ab57e 100644 --- a/frontend/src/utils/inputs/password.js +++ b/frontend/src/utils/inputs/password.js @@ -67,6 +67,7 @@ export class Password { } destroy() { + this._eventManager.cleanUp(); this._iconEl.remove(); this._toggleContainerEl.remove(); this._wrapperEl.remove(); From 945368972da36f4ff0da99f38c71ea9a1c7157d7 Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Thu, 12 Aug 2021 21:47:44 +0200 Subject: [PATCH 55/81] fix(show-hide): storage manager is not cleared --- frontend/src/utils/show-hide/show-hide.js | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/utils/show-hide/show-hide.js b/frontend/src/utils/show-hide/show-hide.js index 411cf1a7d..5739baac9 100644 --- a/frontend/src/utils/show-hide/show-hide.js +++ b/frontend/src/utils/show-hide/show-hide.js @@ -70,7 +70,6 @@ export class ShowHide { } destroy() { - this._storageManager.clear({ location: LOCATION.LOCAL }); this._eventManager.cleanUp(); if (this._element.parentElement.classList.contains(SHOW_HIDE_COLLAPSED_CLASS)) this._element.parentElement.classList.remove(SHOW_HIDE_COLLAPSED_CLASS); From b3b0d6585068ecbc665e819b31c94f4d96a0fec5 Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Thu, 12 Aug 2021 21:53:30 +0200 Subject: [PATCH 56/81] fix(hide-columns): removed clear storage from destroy method --- frontend/src/utils/hide-columns/hide-columns.js | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/utils/hide-columns/hide-columns.js b/frontend/src/utils/hide-columns/hide-columns.js index bb2487c38..79390402a 100644 --- a/frontend/src/utils/hide-columns/hide-columns.js +++ b/frontend/src/utils/hide-columns/hide-columns.js @@ -91,7 +91,6 @@ export class HideColumns { destroy() { this._eventManager.cleanUp(); - this._storageManager.clear(); this._tableUtilContainer.remove(); this._element.classList.remove(HIDE_COLUMNS_INITIALIZED); } From acdeea0b3d8d934a6df131cd44e129f8c87a9c5b Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Thu, 12 Aug 2021 22:03:58 +0200 Subject: [PATCH 57/81] chore(event-manager): uncommented log --- frontend/src/lib/event-manager/event-manager.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/lib/event-manager/event-manager.js b/frontend/src/lib/event-manager/event-manager.js index 851e74eeb..a69965a6f 100644 --- a/frontend/src/lib/event-manager/event-manager.js +++ b/frontend/src/lib/event-manager/event-manager.js @@ -63,10 +63,10 @@ export class EventManager { - //_debugLog() {} - _debugLog(fName, ...args) { - console.log(`[DEBUGLOG] EventManager.${fName}`, { args: args, instance: this }); - } + _debugLog() {} + //_debugLog(fName, ...args) { + // console.log(`[DEBUGLOG] EventManager.${fName}`, { args: args, instance: this }); + //} } export class EventWrapper { From f1c50e137f4f11140c1171dd9d86bc5511b9e771 Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Thu, 12 Aug 2021 22:30:42 +0200 Subject: [PATCH 58/81] fix(storage-manager): clear is working without options as well --- frontend/src/lib/storage-manager/storage-manager.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/frontend/src/lib/storage-manager/storage-manager.js b/frontend/src/lib/storage-manager/storage-manager.js index f2ee68589..2068cd02b 100644 --- a/frontend/src/lib/storage-manager/storage-manager.js +++ b/frontend/src/lib/storage-manager/storage-manager.js @@ -186,14 +186,14 @@ export class StorageManager { } } - clear(options) { + clear(options=this._options) { this._debugLog('clear', options); if (options && options.location !== undefined && !Object.values(LOCATION).includes(options.location)) { throw new Error('StorageManager.clear called with unsupported location option'); } - const locations = options && options.location !== undefined ? [options.location] : LOCATION_SHADOWING; + const locations = ((options !== undefined) && options.location !== undefined)? [options.location] : this._location_shadowing; for (const location of locations) { switch (location) { @@ -204,7 +204,10 @@ export class StorageManager { case LOCATION.WINDOW: return this._clearWindow(); case LOCATION.HISTORY: - return this._clearHistory(options && options.history); + if(options.history) + return this._clearHistory(options && options.history); + else + return; default: console.error('StorageManager.clear cannot clear with unsupported location'); } From 49ac17f8e9efac85c8bf4e581153a9da469a0fb7 Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Thu, 12 Aug 2021 22:34:10 +0200 Subject: [PATCH 59/81] chore(model): moved destroyAll into close method --- frontend/src/utils/modal/modal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/utils/modal/modal.js b/frontend/src/utils/modal/modal.js index f45770d10..8f013572f 100644 --- a/frontend/src/utils/modal/modal.js +++ b/frontend/src/utils/modal/modal.js @@ -137,7 +137,6 @@ export class Modal { _onCloseClicked = (event) => { event.preventDefault(); this._close(); - this._app.utilRegistry.destroyAll(this._element); } _onKeyUp = (event) => { @@ -165,6 +164,7 @@ export class Modal { this._modalsWrapper.classList.remove(MODALS_WRAPPER_OPEN_CLASS); document.removeEventListener('keyup', this._onKeyUp); + this._app.utilRegistry.destroyAll(this._element); }; _fillModal(url) { From a5e666d155e6f9c998f3fb4fadf97d01e68f2b45 Mon Sep 17 00:00:00 2001 From: Sarah Vaupel Date: Tue, 24 Aug 2021 10:40:51 +0200 Subject: [PATCH 60/81] chore(navigate-away-prompt): merging two if conditions --- frontend/src/utils/form/navigate-away-prompt.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/frontend/src/utils/form/navigate-away-prompt.js b/frontend/src/utils/form/navigate-away-prompt.js index bb05238aa..da900ba72 100644 --- a/frontend/src/utils/form/navigate-away-prompt.js +++ b/frontend/src/utils/form/navigate-away-prompt.js @@ -107,13 +107,8 @@ export class NavigateAwayPrompt { // allow the event to happen if the form was not touched by the // user (i.e. if the current FormData is equal to the initial FormData) // or the unload event was initiated by a form submit - if (!formDataHasChanged) + if (!formDataHasChanged || this.unloadDueToSubmit) return; - - - if(this._unloadDueToSubmit) { - return; - } // cancel the unload event. This is the standard to force the prompt to appear. event.preventDefault(); From cbc03f57c56df9c96a84da15a68d26904b75a65f Mon Sep 17 00:00:00 2001 From: Sarah Vaupel Date: Tue, 24 Aug 2021 10:44:24 +0200 Subject: [PATCH 61/81] fix(util-registry): handle negative indices correctly --- frontend/src/services/util-registry/util-registry.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/services/util-registry/util-registry.js b/frontend/src/services/util-registry/util-registry.js index 67cc09702..2e6115dc0 100644 --- a/frontend/src/services/util-registry/util-registry.js +++ b/frontend/src/services/util-registry/util-registry.js @@ -106,7 +106,7 @@ export class UtilRegistry { } let utilIndex = this._activeUtilInstancesWrapped.indexOf(util); util.destroy(); - this._activeUtilInstancesWrapped.splice(utilIndex, 1); + if (utilIndex >= 0) this._activeUtilInstancesWrapped.splice(utilIndex, 1); }); } From 5078b56c16513f3b289bc4824ce0c7914b1a220d Mon Sep 17 00:00:00 2001 From: Sarah Vaupel Date: Tue, 24 Aug 2021 10:46:58 +0200 Subject: [PATCH 62/81] fix(http-client): strict equality check --- frontend/src/services/http-client/http-client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/services/http-client/http-client.js b/frontend/src/services/http-client/http-client.js index 28d7cba21..34fba00dc 100644 --- a/frontend/src/services/http-client/http-client.js +++ b/frontend/src/services/http-client/http-client.js @@ -24,7 +24,7 @@ export class HttpClient { if(this._responseInterceptors.filter(el => el == interceptor).length !== 1) { throw new Error(`Could not find Response Interceptor ${interceptor}.`); } - this._responseInterceptors = this._responseInterceptors.filter(el => el != interceptor); + this._responseInterceptors = this._responseInterceptors.filter(el => el !== interceptor); } _baseUrl; From 5b4ac7587438e7adb21e0ff3d9d629b6ff00263a Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Tue, 24 Aug 2021 12:15:13 +0200 Subject: [PATCH 63/81] fix(util-registry): filtering activeUtilInstances when a util is destroyed --- frontend/src/services/util-registry/util-registry.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/src/services/util-registry/util-registry.js b/frontend/src/services/util-registry/util-registry.js index 2e6115dc0..2b0005a35 100644 --- a/frontend/src/services/util-registry/util-registry.js +++ b/frontend/src/services/util-registry/util-registry.js @@ -104,9 +104,10 @@ export class UtilRegistry { if(DEBUG_MODE > 2) { console.log('Destroying Util: ', {util}); } - let utilIndex = this._activeUtilInstancesWrapped.indexOf(util); util.destroy(); - if (utilIndex >= 0) this._activeUtilInstancesWrapped.splice(utilIndex, 1); + this._activeUtilInstancesWrapped = this._activeUtilInstancesWrapped.filter(utilWrapped => { + return utilWrapped.element === util._element; + }); }); } From f19e9bab9285e090888f0898b47d1d54d18360e0 Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Tue, 24 Aug 2021 14:38:26 +0200 Subject: [PATCH 64/81] chore(tooltips): all tooltips classes are removed in a loop --- frontend/src/utils/tooltips/tooltips.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/frontend/src/utils/tooltips/tooltips.js b/frontend/src/utils/tooltips/tooltips.js index 2a4d77e9b..1bd831c59 100644 --- a/frontend/src/utils/tooltips/tooltips.js +++ b/frontend/src/utils/tooltips/tooltips.js @@ -190,13 +190,12 @@ export class Tooltip { destroy() { this._eventManager.cleanUp(); - if(this._element.classList.contains(TOOLTIP_OPEN_CLASS)) - this._element.classList.remove(TOOLTIP_OPEN_CLASS); - if(this._element.classList.contains('tooltip--right')) - this._element.classList.remove('tooltip--right'); - if(this._element.classList.contains('tooltip--bottom')) - this._element.classList.remove('tooltip--bottom'); this._movementObserver.unobserve(); - this._element.classList.remove(TOOLTIP_INITIALIZED_CLASS); + for (let i = 0; i < this._element.classList.length; i++) { + if (/tooltip--*/.test(this._element.classList[i])) + this._element.classList.remove(this._element.classList.item(i)); + else + i++; + } } }; From 8d0241e727efb5051e5848f2a0c835d7a8d3ae6b Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Tue, 24 Aug 2021 14:39:52 +0200 Subject: [PATCH 65/81] fix(tooltips): removed else case --- frontend/src/utils/tooltips/tooltips.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/frontend/src/utils/tooltips/tooltips.js b/frontend/src/utils/tooltips/tooltips.js index 1bd831c59..ba32bab9e 100644 --- a/frontend/src/utils/tooltips/tooltips.js +++ b/frontend/src/utils/tooltips/tooltips.js @@ -194,8 +194,6 @@ export class Tooltip { for (let i = 0; i < this._element.classList.length; i++) { if (/tooltip--*/.test(this._element.classList[i])) this._element.classList.remove(this._element.classList.item(i)); - else - i++; } } }; From dc0d141cd6bf1394f20e91e2f412b70e32f1ca74 Mon Sep 17 00:00:00 2001 From: Sarah Vaupel Date: Tue, 24 Aug 2021 14:43:34 +0200 Subject: [PATCH 66/81] chore(http-client): check if length is zero --- frontend/src/services/http-client/http-client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/services/http-client/http-client.js b/frontend/src/services/http-client/http-client.js index 34fba00dc..81f72485e 100644 --- a/frontend/src/services/http-client/http-client.js +++ b/frontend/src/services/http-client/http-client.js @@ -21,7 +21,7 @@ export class HttpClient { if (typeof interceptor !== 'function') { throw new Error(`Cannot remove Interceptor ${interceptor}, because it is not of type function`); } - if(this._responseInterceptors.filter(el => el == interceptor).length !== 1) { + if(this._responseInterceptors.filter(el => el == interceptor).length === 0) { throw new Error(`Could not find Response Interceptor ${interceptor}.`); } this._responseInterceptors = this._responseInterceptors.filter(el => el !== interceptor); From 05de310ddd442f1e52ad88ea204179966305cae6 Mon Sep 17 00:00:00 2001 From: Sarah Vaupel Date: Tue, 24 Aug 2021 14:49:45 +0200 Subject: [PATCH 67/81] Apply 2 suggestion(s) to 1 file(s) --- frontend/src/lib/storage-manager/storage-manager.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/lib/storage-manager/storage-manager.js b/frontend/src/lib/storage-manager/storage-manager.js index 2068cd02b..d9ef366ec 100644 --- a/frontend/src/lib/storage-manager/storage-manager.js +++ b/frontend/src/lib/storage-manager/storage-manager.js @@ -204,8 +204,8 @@ export class StorageManager { case LOCATION.WINDOW: return this._clearWindow(); case LOCATION.HISTORY: - if(options.history) - return this._clearHistory(options && options.history); + if(options && options.history) + return this._clearHistory(options.history); else return; default: From c8d36ea52dbef0a2e1aaec6aedaf7edd524975dc Mon Sep 17 00:00:00 2001 From: Johannes Eder Date: Mon, 30 Aug 2021 11:32:24 +0200 Subject: [PATCH 68/81] fix(tooltips): correct regex match --- frontend/src/utils/tooltips/tooltips.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/frontend/src/utils/tooltips/tooltips.js b/frontend/src/utils/tooltips/tooltips.js index ba32bab9e..99b144f7e 100644 --- a/frontend/src/utils/tooltips/tooltips.js +++ b/frontend/src/utils/tooltips/tooltips.js @@ -191,9 +191,7 @@ export class Tooltip { destroy() { this._eventManager.cleanUp(); this._movementObserver.unobserve(); - for (let i = 0; i < this._element.classList.length; i++) { - if (/tooltip--*/.test(this._element.classList[i])) - this._element.classList.remove(this._element.classList.item(i)); - } + const toolTipsRegex = RegExp(/\btooltip--.+\b/, 'g'); + this._element.className = this._element.className.replace(toolTipsRegex, ''); } }; From ebcb23429ff09c56ba4a00a6cb8f082ee83e1fe8 Mon Sep 17 00:00:00 2001 From: ros Date: Sun, 22 Aug 2021 15:49:37 +0200 Subject: [PATCH 69/81] feat(tutoriumsdaten): termin --- src/Handler/Tutorial/Users.hs | 2 +- templates/tutorial-participants.hamlet | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Handler/Tutorial/Users.hs b/src/Handler/Tutorial/Users.hs index f8215a0d9..cc34b1d6a 100644 --- a/src/Handler/Tutorial/Users.hs +++ b/src/Handler/Tutorial/Users.hs @@ -55,7 +55,7 @@ postTUsersR tid ssh csh tutn = do cid <- getKeyBy404 $ TermSchoolCourseShort tid ssh csh table <- makeCourseUserTable cid (Map.fromList $ map (id &&& pure) universeF) isInTut colChoices psValidator (Just csvColChoices) return (tut, table) - + formResult participantRes $ \case (TutorialUserSendMail, selectedUsers) -> do cids <- traverse encrypt $ Set.toList selectedUsers :: Handler [CryptoUUIDUser] diff --git a/templates/tutorial-participants.hamlet b/templates/tutorial-participants.hamlet index 1c6999f09..697d9f6a7 100644 --- a/templates/tutorial-participants.hamlet +++ b/templates/tutorial-participants.hamlet @@ -1,2 +1,8 @@ $newline never +
+
+
_{MsgTableTutorialTime} +
+ ^{occurrencesWidget tutorialTime} +
_{MsgTableTutorialTutors} ^{participantTable} From e972788f540a9ce6c3fdf841313057b62a579d72 Mon Sep 17 00:00:00 2001 From: ros Date: Sun, 29 Aug 2021 14:14:21 +0200 Subject: [PATCH 70/81] feat(tutoriumsdaten): firts draft --- src/Application.hs | 1 + src/Handler/Tutorial/Users.hs | 8 +++++++- templates/tutorial-participants.hamlet | 5 +++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Application.hs b/src/Application.hs index 7d02e6009..e4e2d4b1c 100644 --- a/src/Application.hs +++ b/src/Application.hs @@ -719,3 +719,4 @@ addPWEntry User{ userAuthentication = _, ..} (Text.encodeUtf8 -> pw) = db' $ do PWHashConf{..} <- getsYesod $ view _appAuthPWHash (AuthPWHash . Text.decodeUtf8 -> userAuthentication) <- liftIO $ makePasswordWith pwHashAlgorithm pw pwHashStrength void $ insert User{..} + diff --git a/src/Handler/Tutorial/Users.hs b/src/Handler/Tutorial/Users.hs index cc34b1d6a..eb15e4e84 100644 --- a/src/Handler/Tutorial/Users.hs +++ b/src/Handler/Tutorial/Users.hs @@ -55,7 +55,7 @@ postTUsersR tid ssh csh tutn = do cid <- getKeyBy404 $ TermSchoolCourseShort tid ssh csh table <- makeCourseUserTable cid (Map.fromList $ map (id &&& pure) universeF) isInTut colChoices psValidator (Just csvColChoices) return (tut, table) - + formResult participantRes $ \case (TutorialUserSendMail, selectedUsers) -> do cids <- traverse encrypt $ Set.toList selectedUsers :: Handler [CryptoUUIDUser] @@ -67,6 +67,12 @@ postTUsersR tid ssh csh tutn = do ] addMessageI Success $ MsgTutorialUsersDeregistered nrDel redirect $ CTutorialR tid ssh csh tutn TUsersR + + tutors <- runDB $ + E.select $ E.from $ \(tutor `E.InnerJoin` user) -> do + E.on $ tutor E.^. TutorUser E.==. user E.^. UserId + E.where_ $ tutor E.^. TutorTutorial E.==. E.val tutid + return user let heading = prependCourseTitle tid ssh csh $ CI.original tutorialName siteLayoutMsg heading $ do diff --git a/templates/tutorial-participants.hamlet b/templates/tutorial-participants.hamlet index 697d9f6a7..a5ca27d35 100644 --- a/templates/tutorial-participants.hamlet +++ b/templates/tutorial-participants.hamlet @@ -5,4 +5,9 @@ $newline never
^{occurrencesWidget tutorialTime}
_{MsgTableTutorialTutors} +
+
    + $forall (Entity _ User{userDisplayName, userDisplayEmail, userSurname}) <- tutors +
  • + ^{nameEmailWidget userDisplayEmail userDisplayName userSurname} ^{participantTable} From d4a73e699a399b02cadcea03e614c789671ee6d1 Mon Sep 17 00:00:00 2001 From: ros Date: Sun, 29 Aug 2021 14:20:39 +0200 Subject: [PATCH 71/81] feat(tutoriumsdaten): application restore --- src/Application.hs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Application.hs b/src/Application.hs index e4e2d4b1c..7d02e6009 100644 --- a/src/Application.hs +++ b/src/Application.hs @@ -719,4 +719,3 @@ addPWEntry User{ userAuthentication = _, ..} (Text.encodeUtf8 -> pw) = db' $ do PWHashConf{..} <- getsYesod $ view _appAuthPWHash (AuthPWHash . Text.decodeUtf8 -> userAuthentication) <- liftIO $ makePasswordWith pwHashAlgorithm pw pwHashStrength void $ insert User{..} - From 08f9bc06974410bf2ed128a22e7c243336e44727 Mon Sep 17 00:00:00 2001 From: Sarah Vaupel Date: Mon, 30 Aug 2021 17:04:12 +0200 Subject: [PATCH 72/81] chore(release): 25.22.0 --- CHANGELOG.md | 36 ++++++++++++++++++++++++++++++++++++ package-lock.json | 2 +- package.json | 2 +- package.yaml | 2 +- 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22a504a9a..dcf5299d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,42 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [25.22.0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.21.0...v25.22.0) (2021-08-30) + + +### Features + +* **event-manager:** added method to register a list of listeners ([1a8fb23](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/1a8fb230441f73b9b1fe593f4df1005a06d9628e)) +* **event-manager:** mutation observers can be managed via the event manager ([34b4f48](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/34b4f48386c8fff4569b12eb3f7919e7c77d33c0)) +* **http-client:** added possibility to remove specific interceptors ([0823df3](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/0823df33b5f157af290aca9a65f1df429048e53b)) +* **tutoriumsdaten:** application restore ([d4a73e6](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/d4a73e699a399b02cadcea03e614c789671ee6d1)) +* **tutoriumsdaten:** firts draft ([e972788](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/e972788f540a9ce6c3fdf841313057b62a579d72)) +* **tutoriumsdaten:** termin ([ebcb234](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/ebcb23429ff09c56ba4a00a6cb8f082ee83e1fe8)) +* **util_registry:** impelmented destroyAll(scope) method in the utilRegistry ([f1ef2e5](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/f1ef2e5ec776ad8a1fb7737eb0ed4f79218afd61)) +* implemented an event manager ([c1c3536](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/c1c35369d1ae017971e6d8cbafc06844f02fd00d)) + + +### Bug Fixes + +* **async-form:** destroy all after response is processed ([14a16c7](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/14a16c7283483ff22ce22b070a430a61d24a7f35)) +* **communication-recipients:** fixed undefined error with context and a few minor issues ([03ac803](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/03ac80342e0f3fcb1db2adf34f86a2c0d8fabf0f)) +* **enter-is-tab.js:** implemented destroy method in enter-is-tab Util ([d1b9952](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/d1b995269060c670d85f715671bfc9947b9f3e9a)) +* **hide-columns:** removed clear storage from destroy method ([b3b0d65](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/b3b0d6585068ecbc665e819b31c94f4d96a0fec5)) +* **hide-colums:** small fix ([50a3ac1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/50a3ac1790f26c1e6f983d3687352d7811173110)) +* **http-client:** strict equality check ([5078b56](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/5078b56c16513f3b289bc4824ce0c7914b1a220d)) +* **interactive-fieldset:** small fix ([4c2c683](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/4c2c68327e75f5f51271853159c232fdd7bba21e)) +* **navigate-away-promp:** removed unnecessary destroyAll ([7a07159](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/7a0715906c8336ed64bbfabdf614746ade9f661f)) +* **password:** added cleanUP ([204ce39](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/204ce39f7c2f9c405971aa59da068a5389dda417)) +* **show-hide:** storage manager is not cleared ([9453689](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/945368972da36f4ff0da99f38c71ea9a1c7157d7)) +* **storage-manager:** clear is working without options as well ([f1c50e1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/f1c50e137f4f11140c1171dd9d86bc5511b9e771)) +* **tooltips:** correct regex match ([c8d36ea](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/c8d36ea52dbef0a2e1aaec6aedaf7edd524975dc)) +* **tooltips:** removed else case ([8d0241e](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/8d0241e727efb5051e5848f2a0c835d7a8d3ae6b)) +* **util-registry:** filtering activeUtilInstances when a util is destroyed ([5b4ac75](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/5b4ac7587438e7adb21e0ff3d9d629b6ff00263a)) +* **util-registry:** handle negative indices correctly ([cbc03f5](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/cbc03f57c56df9c96a84da15a68d26904b75a65f)) +* fixed a few minor issues ([6320cd9](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/6320cd927a84445f056dc782fb440d276cb26009)) +* prompt not shwowing up after submit/close ([abe8415](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/abe84156d508dca8fce549b24d5902d24afc0dbf)) +* smaller fixes and typos ([1f978e6](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/1f978e65a82a91fb728a7ee2970a4fd9e6beb521)) + ## [25.21.0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.20.2...v25.21.0) (2021-08-20) diff --git a/package-lock.json b/package-lock.json index 38b149cfa..517e89556 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "uni2work", - "version": "25.21.0", + "version": "25.22.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 727983d52..2e56de4a0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "uni2work", - "version": "25.21.0", + "version": "25.22.0", "description": "", "keywords": [], "author": "", diff --git a/package.yaml b/package.yaml index 2793c89b4..5c6556bfb 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: uniworx -version: 25.21.0 +version: 25.22.0 dependencies: - base - yesod From f1fe4447fbe7e96e55aaf284c7083338b5135ab6 Mon Sep 17 00:00:00 2001 From: Sarah Vaupel Date: Fri, 17 Sep 2021 13:21:15 +0200 Subject: [PATCH 73/81] fix(course-admins): display course admins as admins instead of assistants --- .../categories/courses/courses/de-de-formal.msg | 1 + .../uniworx/categories/courses/courses/en-eu.msg | 1 + src/Handler/Course/Show.hs | 12 ++++++------ templates/course.hamlet | 7 +++++++ 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/messages/uniworx/categories/courses/courses/de-de-formal.msg b/messages/uniworx/categories/courses/courses/de-de-formal.msg index 92823ea08..2e1880882 100644 --- a/messages/uniworx/categories/courses/courses/de-de-formal.msg +++ b/messages/uniworx/categories/courses/courses/de-de-formal.msg @@ -187,6 +187,7 @@ LecturerFor: Dozent:in LecturersFor: Dozierende AssistantFor: Assistent:in AssistantsFor: Assistent:innen +CourseAdminFor: Kursadministration TutorsFor n@Int: #{pluralDE n "Tutor:in" "Tutor:innen"} CorrectorsFor n@Int: #{pluralDE n "Korrektor:in" "Korrektor:innen"} CourseParticipantsHeading: Kursteilnehmer:innen diff --git a/messages/uniworx/categories/courses/courses/en-eu.msg b/messages/uniworx/categories/courses/courses/en-eu.msg index da740b3ae..c4eda4efc 100644 --- a/messages/uniworx/categories/courses/courses/en-eu.msg +++ b/messages/uniworx/categories/courses/courses/en-eu.msg @@ -187,6 +187,7 @@ LecturerFor: Lecturer LecturersFor: Lecturers AssistantFor: Assistant AssistantsFor: Assistants +CourseAdminFor: Course administration TutorsFor n: #{pluralEN n "Tutor" "Tutors"} CorrectorsFor n: #{pluralEN n "Corrector" "Correctors"} CourseParticipantsHeading: Course participants diff --git a/src/Handler/Course/Show.hs b/src/Handler/Course/Show.hs index fab484c0b..1f25a0b29 100644 --- a/src/Handler/Course/Show.hs +++ b/src/Handler/Course/Show.hs @@ -30,7 +30,7 @@ getCShowR :: TermId -> SchoolId -> CourseShorthand -> Handler Html getCShowR tid ssh csh = do mbAid <- maybeAuthId now <- liftIO getCurrentTime - (cid,course,courseVisible,schoolName,participants,registration,lecturers,assistants,correctors,tutors,mAllocation,mApplicationTemplate,mApplication,news,events,submissionGroup,hasAllocationRegistrationOpen,mayReRegister,(mayViewSheets, mayViewAnySheet), (mayViewMaterials, mayViewAnyMaterial)) <- runDB . maybeT notFound $ do + (cid,course,courseVisible,schoolName,participants,registration,lecturers,assistants,administrators,correctors,tutors,mAllocation,mApplicationTemplate,mApplication,news,events,submissionGroup,hasAllocationRegistrationOpen,mayReRegister,(mayViewSheets, mayViewAnySheet), (mayViewMaterials, mayViewAnyMaterial)) <- runDB . maybeT notFound $ do [(E.Entity cid course, E.Value courseVisible, E.Value schoolName, E.Value participants, fmap entityVal -> registration, E.Value hasAllocationRegistrationOpen)] <- lift . E.select . E.from $ \((school `E.InnerJoin` course) `E.LeftOuterJoin` allocation `E.LeftOuterJoin` participant) -> do @@ -62,10 +62,10 @@ getCShowR tid ssh csh = do E.orderBy [ E.asc $ user E.^. UserSurname, E.asc $ user E.^. UserDisplayName ] return ( lecturer E.^. LecturerType , user E.^. UserDisplayEmail, user E.^. UserDisplayName, user E.^. UserSurname) - let partStaff :: (LecturerType, UserEmail, Text, Text) -> Either (UserEmail, Text, Text) (UserEmail, Text, Text) - partStaff (CourseLecturer ,name,surn,mail) = Right (name,surn,mail) - partStaff (_courseAssistant,name,surn,mail) = Left (name,surn,mail) - (assistants,lecturers) = partitionWith partStaff $ map $(unValueN 4) staff + let + (administrators', regularStaff) = partition ((==) CourseAdministrator . view _1) $ map (\(E.Value lecType, E.Value lecName, E.Value lecSurn, E.Value lecMail) -> (lecType,(lecName,lecSurn,lecMail))) staff + (lecturers', assistants') = partition ((==) CourseLecturer . view _1) regularStaff + (administrators, lecturers, assistants) = (view _2 <$> administrators', view _2 <$> lecturers', view _2 <$> assistants') correctors <- fmap (map $(unValueN 3)) . lift . E.select $ E.from $ \(sheet `E.InnerJoin` sheetCorrector `E.InnerJoin` user) -> E.distinctOnOrderBy [E.asc $ user E.^. UserSurname, E.asc $ user E.^. UserDisplayName, E.asc $ user E.^. UserEmail ] $ do E.on $ sheetCorrector E.^. SheetCorrectorUser E.==. user E.^. UserId E.on $ sheetCorrector E.^. SheetCorrectorSheet E.==. sheet E.^. SheetId @@ -142,7 +142,7 @@ getCShowR tid ssh csh = do return $ material E.^. MaterialName mayViewAnyMaterial <- lift . anyM materials $ \(E.Value mnm) -> hasReadAccessTo $ CMaterialR tid ssh csh mnm MShowR - return (cid,course,courseVisible,schoolName,participants,registration,lecturers,assistants,correctors,tutors,mAllocation,mApplicationTemplate,mApplication,news,events,submissionGroup,hasAllocationRegistrationOpen,mayReRegister, (mayViewSheets, mayViewAnySheet), (mayViewMaterials, mayViewAnyMaterial)) + return (cid,course,courseVisible,schoolName,participants,registration,lecturers,assistants,administrators,correctors,tutors,mAllocation,mApplicationTemplate,mApplication,news,events,submissionGroup,hasAllocationRegistrationOpen,mayReRegister, (mayViewSheets, mayViewAnySheet), (mayViewMaterials, mayViewAnyMaterial)) let mDereg' = maybe id min (allocationOverrideDeregister =<< mAllocation) <$> courseDeregisterUntil course mDereg <- traverse (formatTime SelFormatDateTime) mDereg' diff --git a/templates/course.hamlet b/templates/course.hamlet index 2205d1f73..de6452829 100644 --- a/templates/course.hamlet +++ b/templates/course.hamlet @@ -93,6 +93,13 @@ $# #{summary}
      $forall assi <- assistants
    • ^{nameEmailWidget' assi} + $with numadmins <- length administrators + $if numadmins /= 0 +
      _{MsgCourseAdminFor} +
      +
        + $forall admin <- administrators +
      • ^{nameEmailWidget' admin} $with numtutor <- length tutors $if numtutor /= 0 From f82cf602d1af6058971a59f64afffaa55fffdbc6 Mon Sep 17 00:00:00 2001 From: Sarah Vaupel Date: Fri, 1 Oct 2021 23:33:23 +0200 Subject: [PATCH 74/81] chore: update core team --- templates/i18n/help-instructions/de-de-formal.hamlet | 12 ++++++------ templates/i18n/help-instructions/en-eu.hamlet | 12 +++++++----- templates/i18n/imprint/de-de-formal.hamlet | 6 +++--- templates/i18n/imprint/en.hamlet | 6 +++--- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/templates/i18n/help-instructions/de-de-formal.hamlet b/templates/i18n/help-instructions/de-de-formal.hamlet index 99370c184..f9dce6a42 100644 --- a/templates/i18n/help-instructions/de-de-formal.hamlet +++ b/templates/i18n/help-instructions/de-de-formal.hamlet @@ -2,14 +2,14 @@ $newline never

        - Bitte bedenken Sie beim Stellen Ihrer Anfrage, dass das # - Uni2work-Kernteam aktuell aus Sarah Vaupel und Gregor Kleen besteht # - und zwei Personen nicht hinreichend sind um in allen Fällen eine # - zeitnahe Bearbeitung Ihres Anliegens zu garantieren. + Bitte bedenken Sie beim Stellen Ihrer Anfrage, dass das Uni2work-Kernteam aus # + Sarah Vaupel # + besteht und # + eine Person nicht hinreichend ist, # + um in allen Fällen eine zeitnahe Bearbeitung Ihres Anliegens zu garantieren.

        Falls sich Ihr Anliegen auf eine konkrete Veranstaltung bezieht, # ziehen Sie bitte auch in Betracht (insbesondere bei zeitkritischen # - Anliegen wie z.B. Abgaben) sich direkt an die Kursverwalter zu # - wenden. + Anliegen wie z.B. Abgaben) sich direkt an die Kursverwalter zu wenden. diff --git a/templates/i18n/help-instructions/en-eu.hamlet b/templates/i18n/help-instructions/en-eu.hamlet index 65e205bae..fe2b19102 100644 --- a/templates/i18n/help-instructions/en-eu.hamlet +++ b/templates/i18n/help-instructions/en-eu.hamlet @@ -2,14 +2,16 @@ $newline never

        - When formulating your request please consider that the Uni2work core # - team currently consists of Sarah Vaupel and Gregor Kleen and that # - two people are not enough to guarantee a timely answer in all cases. + When formulating your request, please consider that the Uni2work core team consists of # + Sarah Vaupel # + and that # + one person is # + not enough to guarantee a timely answer in all cases.

        If your request is related to a specific course, please also # consider contacting the relevant course administrators as well. # - Especially if your request is time sensitive (e.g. submitting for an # - exercise sheet). + Especially if your request is time sensitive (e.g. submitting for # + an exercise sheet). diff --git a/templates/i18n/imprint/de-de-formal.hamlet b/templates/i18n/imprint/de-de-formal.hamlet index d415a5780..af4c29eca 100644 --- a/templates/i18n/imprint/de-de-formal.hamlet +++ b/templates/i18n/imprint/de-de-formal.hamlet @@ -3,12 +3,12 @@ $newline never

        Inhalt
          -
        • Gregor Kleen & Sarah Vaupel +
        • Sarah Vaupel
        • Oettingenstraße 67
        • D-80538 München +
        • Raum L101
        • E-Mail: ^{mailtoHtml "uni2work@ifi.lmu.de"} -
        • Telefon (Gregor Kleen): +49 (0) 89 / 2180 - 9139 -
        • Telefon (Sarah Vaupel): — +
        • Telefon: +49 (0) 89 / 2180 - 9139

          Jugendschutz
            diff --git a/templates/i18n/imprint/en.hamlet b/templates/i18n/imprint/en.hamlet index c2506db40..a005c7396 100644 --- a/templates/i18n/imprint/en.hamlet +++ b/templates/i18n/imprint/en.hamlet @@ -3,12 +3,12 @@ $newline never

            Contents
              -
            • Gregor Kleen & Sarah Vaupel +
            • Sarah Vaupel
            • Oettingenstraße 67
            • D-80538 München (Germany) +
            • Room L101
            • E-Mail: ^{mailtoHtml "uni2work@ifi.lmu.de"} -
            • Telefon (Gregor Kleen): +49 (0) 89 / 2180 - 9139 -
            • Telefon (Sarah Vaupel): — +
            • Phone: +49 (0) 89 / 2180 - 9139

              Youth Protection
                From 1eed96014f933d6e7ead8bcb1f5ca475382906e8 Mon Sep 17 00:00:00 2001 From: Sarah Vaupel Date: Sat, 2 Oct 2021 23:18:48 +0200 Subject: [PATCH 75/81] chore(release): 25.22.1 --- CHANGELOG.md | 7 +++++++ package-lock.json | 2 +- package.json | 2 +- package.yaml | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dcf5299d5..2e36d2589 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [25.22.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.22.0...v25.22.1) (2021-10-02) + + +### Bug Fixes + +* **course-admins:** display course admins as admins instead of assistants ([f1fe444](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/f1fe4447fbe7e96e55aaf284c7083338b5135ab6)) + ## [25.22.0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.21.0...v25.22.0) (2021-08-30) diff --git a/package-lock.json b/package-lock.json index 517e89556..52a998cb5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "uni2work", - "version": "25.22.0", + "version": "25.22.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 2e56de4a0..71a8c4795 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "uni2work", - "version": "25.22.0", + "version": "25.22.1", "description": "", "keywords": [], "author": "", diff --git a/package.yaml b/package.yaml index 5c6556bfb..d12ae451f 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: uniworx -version: 25.22.0 +version: 25.22.1 dependencies: - base - yesod From 96d3b1207bd55682c17ff398a68972320bf5c711 Mon Sep 17 00:00:00 2001 From: Sarah Vaupel Date: Wed, 13 Oct 2021 14:42:12 +0200 Subject: [PATCH 76/81] chore(workflows): bump workflows --- testdata/workflows | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testdata/workflows b/testdata/workflows index 1a788c67f..39640b53f 160000 --- a/testdata/workflows +++ b/testdata/workflows @@ -1 +1 @@ -Subproject commit 1a788c67fe98cadf1e29b0e328072437955fd660 +Subproject commit 39640b53fb43578f35d17f7a0b6cdf7e3cdaa0bd From 9b45d007bcd26745f97cce4f5e0648db21d259cc Mon Sep 17 00:00:00 2001 From: Sarah Vaupel Date: Wed, 13 Oct 2021 14:59:12 +0200 Subject: [PATCH 77/81] chore(release): 25.22.2 --- CHANGELOG.md | 2 ++ package-lock.json | 2 +- package.json | 2 +- package.yaml | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e36d2589..2f742526c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [25.22.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.22.1...v25.22.2) (2021-10-13) + ## [25.22.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.22.0...v25.22.1) (2021-10-02) diff --git a/package-lock.json b/package-lock.json index 52a998cb5..38fe5251b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "uni2work", - "version": "25.22.1", + "version": "25.22.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 71a8c4795..1902d7a63 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "uni2work", - "version": "25.22.1", + "version": "25.22.2", "description": "", "keywords": [], "author": "", diff --git a/package.yaml b/package.yaml index d12ae451f..32949f723 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: uniworx -version: 25.22.1 +version: 25.22.2 dependencies: - base - yesod From adf9709567d9a320f2c17d3c5dde940c2f9d8862 Mon Sep 17 00:00:00 2001 From: Sarah Vaupel Date: Thu, 21 Oct 2021 14:58:08 +0200 Subject: [PATCH 78/81] fix(navigation): always link workflows nav to instances --- src/Foundation/Navigation.hs | 87 +++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 42 deletions(-) diff --git a/src/Foundation/Navigation.hs b/src/Foundation/Navigation.hs index 28303797b..a1133b8e3 100644 --- a/src/Foundation/Navigation.hs +++ b/src/Foundation/Navigation.hs @@ -573,8 +573,8 @@ navLinkAccess NavLink{..} = case navAccess' of defaultLinks :: ( MonadHandler m , HandlerSite m ~ UniWorX - , MonadThrow m - , WithRunDB SqlReadBackend (HandlerFor UniWorX) m + -- , MonadThrow m + -- , WithRunDB SqlReadBackend (HandlerFor UniWorX) m , BearerAuthSite UniWorX ) => m [Nav] defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the header. @@ -761,12 +761,14 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , do guardVolatile clusterVolatileWorkflowsEnabled - authCtx <- getAuthContext - (haveInstances, haveWorkflows) <- lift . memcachedBy (Just . Right $ 2 * diffMinute) (NavCacheHaveTopWorkflowsInstances authCtx) . useRunDB $ (,) - <$> haveTopWorkflowInstances - <*> haveTopWorkflowWorkflows + -- authCtx <- getAuthContext + -- (haveInstances, haveWorkflows) <- lift . memcachedBy (Just . Right $ 2 * diffMinute) (NavCacheHaveTopWorkflowsInstances authCtx) . useRunDB $ (,) + -- <$> haveTopWorkflowInstances + -- <*> haveTopWorkflowWorkflows - if | haveInstances -> return NavHeader + mUserId <- maybeAuthId + -- if | haveInstances -> return NavHeader + if | isJust mUserId -> return NavHeader { navHeaderRole = NavHeaderPrimary , navIcon = IconMenuWorkflows , navLink = NavLink @@ -778,18 +780,18 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navForceActive = False } } - | haveWorkflows -> return NavHeader - { navHeaderRole = NavHeaderPrimary - , navIcon = IconMenuWorkflows - , navLink = NavLink - { navLabel = MsgMenuTopWorkflowWorkflowListHeader - , navRoute = TopWorkflowWorkflowListR - , navAccess' = NavAccessTrue - , navType = NavTypeLink { navModal = False } - , navQuick' = mempty - , navForceActive = False - } - } + -- | haveWorkflows -> return NavHeader + -- { navHeaderRole = NavHeaderPrimary + -- , navIcon = IconMenuWorkflows + -- , navLink = NavLink + -- { navLabel = MsgMenuTopWorkflowWorkflowListHeader + -- , navRoute = TopWorkflowWorkflowListR + -- , navAccess' = NavAccessTrue + -- , navType = NavTypeLink { navModal = False } + -- , navQuick' = mempty + -- , navForceActive = False + -- } + -- } | otherwise -> mzero , return NavHeaderContainer { navHeaderRole = NavHeaderPrimary @@ -2730,34 +2732,35 @@ haveWorkflowWorkflows rScope = hoist liftHandler . withReaderT (projectBackend @ lift $ anyM roles evalRole -haveTopWorkflowInstances, haveTopWorkflowWorkflows +-- haveTopWorkflowInstances, +haveTopWorkflowWorkflows :: ( MonadHandler m, HandlerSite m ~ UniWorX , BackendCompatible SqlReadBackend backend , BearerAuthSite UniWorX ) => ReaderT backend m Bool -haveTopWorkflowInstances = hoist liftHandler . withReaderT (projectBackend @SqlReadBackend) . $cachedHere . maybeT (return False) $ do - roles <- memcachedBy @(Set ((RouteWorkflowScope, WorkflowInstanceName), WorkflowRole UserId)) (Just $ Right diffDay) NavCacheHaveTopWorkflowInstancesRoles $ do - let - getInstances = E.selectSource . E.from $ \workflowInstance -> do - E.where_ . isTopWorkflowScopeSql $ workflowInstance E.^. WorkflowInstanceScope - return workflowInstance - instanceRoles (Entity _ WorkflowInstance{..}) = do - rScope <- toRouteWorkflowScope $ _DBWorkflowScope # workflowInstanceScope - wiGraph <- lift $ getSharedIdWorkflowGraph workflowInstanceGraph - return . Set.mapMonotonic ((rScope, workflowInstanceName), ) . fold $ do - WGN{..} <- wiGraph ^.. _wgNodes . folded - WorkflowGraphEdgeInitial{..} <- wgnEdges ^.. folded - return wgeActors - runConduit $ transPipe lift getInstances .| C.foldMapM instanceRoles - - let - evalRole :: _ -> ReaderT SqlReadBackend (HandlerFor UniWorX) Bool - evalRole ((rScope, win), role) = do - let route = _WorkflowScopeRoute # (rScope, WorkflowInstanceR win WIInitiateR) - is _Authorized <$> hasWorkflowRole Nothing role route False - - lift $ anyM roles evalRole +-- haveTopWorkflowInstances = hoist liftHandler . withReaderT (projectBackend @SqlReadBackend) . $cachedHere . maybeT (return False) $ do +-- roles <- memcachedBy @(Set ((RouteWorkflowScope, WorkflowInstanceName), WorkflowRole UserId)) (Just $ Right diffDay) NavCacheHaveTopWorkflowInstancesRoles $ do +-- let +-- getInstances = E.selectSource . E.from $ \workflowInstance -> do +-- E.where_ . isTopWorkflowScopeSql $ workflowInstance E.^. WorkflowInstanceScope +-- return workflowInstance +-- instanceRoles (Entity _ WorkflowInstance{..}) = do +-- rScope <- toRouteWorkflowScope $ _DBWorkflowScope # workflowInstanceScope +-- wiGraph <- lift $ getSharedIdWorkflowGraph workflowInstanceGraph +-- return . Set.mapMonotonic ((rScope, workflowInstanceName), ) . fold $ do +-- WGN{..} <- wiGraph ^.. _wgNodes . folded +-- WorkflowGraphEdgeInitial{..} <- wgnEdges ^.. folded +-- return wgeActors +-- runConduit $ transPipe lift getInstances .| C.foldMapM instanceRoles +-- +-- let +-- evalRole :: _ -> ReaderT SqlReadBackend (HandlerFor UniWorX) Bool +-- evalRole ((rScope, win), role) = do +-- let route = _WorkflowScopeRoute # (rScope, WorkflowInstanceR win WIInitiateR) +-- is _Authorized <$> hasWorkflowRole Nothing role route False +-- +-- lift $ anyM roles evalRole haveTopWorkflowWorkflows = hoist liftHandler . withReaderT (projectBackend @SqlReadBackend) . $cachedHere . maybeT (return False) $ do roles <- memcachedBy (Just $ Right diffDay) NavCacheHaveTopWorkflowWorkflowsRoles $ do let From 9f939ba805b89197a7cc898d47c747bc456e75c0 Mon Sep 17 00:00:00 2001 From: Sarah Vaupel Date: Thu, 21 Oct 2021 15:12:24 +0200 Subject: [PATCH 79/81] chore(release): 25.22.3 --- CHANGELOG.md | 7 +++++++ package-lock.json | 2 +- package.json | 2 +- package.yaml | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f742526c..58dfb13b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [25.22.3](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.22.2...v25.22.3) (2021-10-21) + + +### Bug Fixes + +* **navigation:** always link workflows nav to instances ([adf9709](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/adf9709567d9a320f2c17d3c5dde940c2f9d8862)) + ## [25.22.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.22.1...v25.22.2) (2021-10-13) ## [25.22.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.22.0...v25.22.1) (2021-10-02) diff --git a/package-lock.json b/package-lock.json index 38fe5251b..78874c4d4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "uni2work", - "version": "25.22.2", + "version": "25.22.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 1902d7a63..a93525c62 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "uni2work", - "version": "25.22.2", + "version": "25.22.3", "description": "", "keywords": [], "author": "", diff --git a/package.yaml b/package.yaml index 32949f723..c3c7d0ae7 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: uniworx -version: 25.22.2 +version: 25.22.3 dependencies: - base - yesod From 29c54db06f01659a3a6419009964a85cd11d5441 Mon Sep 17 00:00:00 2001 From: Sarah Vaupel Date: Tue, 26 Oct 2021 22:47:03 +0200 Subject: [PATCH 80/81] fix(routes): make access to workflows free --- routes | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/routes b/routes index c7299e84c..8051d646f 100644 --- a/routes +++ b/routes @@ -78,7 +78,7 @@ /global-workflows/instances/#WorkflowInstanceName GlobalWorkflowInstanceR: /edit GWIEditR GET POST /delete GWIDeleteR GET POST - /workflows GWIWorkflowsR GET !¬empty + /workflows GWIWorkflowsR GET !free /initiate GWIInitiateR GET POST !workflow /update GWIUpdateR POST /global-workflows GlobalWorkflowWorkflowListR GET !free @@ -145,7 +145,7 @@ /workflows/instances/#WorkflowInstanceName SchoolWorkflowInstanceR: /edit SWIEditR GET POST /delete SWIDeleteR GET POST - /workflows SWIWorkflowsR GET !¬empty + /workflows SWIWorkflowsR GET !free /initiate SWIInitiateR GET POST !workflow /update SWIUpdateR POST /workflows SchoolWorkflowWorkflowListR GET !free From b33b50ba0e16fdffb66817f619d845428334ed57 Mon Sep 17 00:00:00 2001 From: Sarah Vaupel Date: Tue, 26 Oct 2021 23:32:24 +0200 Subject: [PATCH 81/81] chore(release): 25.22.4 --- CHANGELOG.md | 7 +++++++ package-lock.json | 2 +- package.json | 2 +- package.yaml | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58dfb13b0..5e9f80e91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [25.22.4](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.22.3...v25.22.4) (2021-10-26) + + +### Bug Fixes + +* **routes:** make access to workflows free ([29c54db](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/29c54db06f01659a3a6419009964a85cd11d5441)) + ## [25.22.3](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.22.2...v25.22.3) (2021-10-21) diff --git a/package-lock.json b/package-lock.json index 78874c4d4..3113011ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "uni2work", - "version": "25.22.3", + "version": "25.22.4", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index a93525c62..ae47ea5a3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "uni2work", - "version": "25.22.3", + "version": "25.22.4", "description": "", "keywords": [], "author": "", diff --git a/package.yaml b/package.yaml index c3c7d0ae7..43bc3cbc1 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: uniworx -version: 25.22.3 +version: 25.22.4 dependencies: - base - yesod