From 1b2d980971597ff6ba909d68e97a3ada03bac868 Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Sun, 3 Mar 2019 12:18:10 +0100 Subject: [PATCH 1/4] alter ids for dynamic modal content to avoid name collisions closes #308 --- static/js/utils/modal.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/static/js/utils/modal.js b/static/js/utils/modal.js index 63d7471d3..643704e45 100644 --- a/static/js/utils/modal.js +++ b/static/js/utils/modal.js @@ -112,10 +112,23 @@ if (previousModalContent) { previousModalContent.remove(); } + + modalContent = withPrefixedInputIDs(modalContent); modalElement.insertBefore(modalContent, null); setupForm(); } + function withPrefixedInputIDs(modalContent) { + var idAttrs = ['id', 'for', 'data-conditional-id']; + idAttrs.forEach(function(attr) { + modalContent.querySelectorAll('[' + attr + ']').forEach(function(input) { + var value = modalElement.id + '__' + input.getAttribute(attr); + input.setAttribute(attr, value); + }); + }); + return modalContent; + } + function keyupHandler(event) { if (event.key === 'Escape') { close(); From 57f9e058f339a9774b1ae252f8edf5db4ff0084e Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Sun, 3 Mar 2019 14:54:38 +0100 Subject: [PATCH 2/4] js utils now return function to destroy them --- static/js/utils/alerts.js | 4 ++++ static/js/utils/asidenav.js | 4 ++++ static/js/utils/asyncForm.js | 4 ++++ static/js/utils/asyncTable.js | 25 +++++++++++++------ static/js/utils/asyncTableFilter.js | 4 ++++ static/js/utils/checkAll.js | 14 ++++++++++- static/js/utils/form.js | 34 ++++++++++++++++++++++---- static/js/utils/inputs.js | 37 +++++++++++++++++++++++++---- static/js/utils/modal.js | 14 ++++++++++- static/js/utils/showHide.js | 4 ++++ static/js/utils/tabber.js | 4 ++++ 11 files changed, 129 insertions(+), 19 deletions(-) diff --git a/static/js/utils/alerts.js b/static/js/utils/alerts.js index d52a47376..03f36b7aa 100644 --- a/static/js/utils/alerts.js +++ b/static/js/utils/alerts.js @@ -87,5 +87,9 @@ initToggler(); alertElements.forEach(initAlert); + + return { + destroy: function() {}, + }; }; })(); diff --git a/static/js/utils/asidenav.js b/static/js/utils/asidenav.js index 154232109..cbe43fb4f 100644 --- a/static/js/utils/asidenav.js +++ b/static/js/utils/asidenav.js @@ -55,5 +55,9 @@ initFavoritesButton(); initAsidenavSubmenus(); + + return { + destroy: function() {}, + }; }; })(); diff --git a/static/js/utils/asyncForm.js b/static/js/utils/asyncForm.js index 7917012a9..0ce2ed986 100644 --- a/static/js/utils/asyncForm.js +++ b/static/js/utils/asyncForm.js @@ -56,5 +56,9 @@ } setup(); + + return { + destroy: function() {}, + }; }; })(); diff --git a/static/js/utils/asyncTable.js b/static/js/utils/asyncTable.js index 0f8058e6b..7e7e88855 100644 --- a/static/js/utils/asyncTable.js +++ b/static/js/utils/asyncTable.js @@ -21,6 +21,8 @@ var pagesizeForm; var scrollTable; + var utilInstances = []; + function init() { var table = wrapper.querySelector('#' + tableIdent); @@ -47,13 +49,13 @@ pagesizeForm = wrapper.querySelector('#' + tableIdent + '-pagesize-form'); // check all - window.utils.setup('checkAll', wrapper); + utilInstances.push(window.utils.setup('checkAll', wrapper)); // filter var filterForm = wrapper.querySelector('.' + TABLE_FILTER_FORM_CLASS); if (filterForm) { options.updateTableFrom = updateTableFrom; - window.utils.setup('asyncTableFilter', filterForm, options); + utilInstances.push(window.utils.setup('asyncTableFilter', filterForm, options)); } // take options into account @@ -177,9 +179,8 @@ wrapper.classList.remove(JS_INITIALIZED_CLASS); wrapper.classList.add(ASYNC_TABLE_CONTENT_CHANGED_CLASS); - // setup the wrapper and its components to behave async again - window.utils.teardown('asyncTable'); - window.utils.teardown('form'); + destroyUtils(); + // merge global options and table specific options var resetOptions = {}; Object.keys(options) @@ -205,13 +206,23 @@ window.utils.setup('asyncTable', wrapper, combinedOptions); Array.from(wrapper.querySelectorAll('form')).forEach(function(form) { - window.utils.setup('form', form); + utilInstances.push(window.utils.setup('form', form)); }); Array.from(wrapper.querySelectorAll('.modal')).forEach(function(modal) { - window.utils.setup('modal', modal); + utilInstances.push(window.utils.setup('modal', modal)); + }); + } + + function destroyUtils() { + utilInstances.forEach(function(utilInstance) { + utilInstance.destroy(); }); } init(); + + return { + destroy: destroyUtils, + }; }; })(); diff --git a/static/js/utils/asyncTableFilter.js b/static/js/utils/asyncTableFilter.js index 6ed3ae8df..478eba9d1 100644 --- a/static/js/utils/asyncTableFilter.js +++ b/static/js/utils/asyncTableFilter.js @@ -157,5 +157,9 @@ } setup(); + + return { + destroy: function() {}, + }; } })(); diff --git a/static/js/utils/checkAll.js b/static/js/utils/checkAll.js index 5b59a3f8f..c885ee100 100644 --- a/static/js/utils/checkAll.js +++ b/static/js/utils/checkAll.js @@ -22,6 +22,8 @@ var checkboxColumn = []; var checkAllCheckbox = null; + var utilInstances = []; + function init() { columns = gatherColumns(wrapper); @@ -80,7 +82,7 @@ checkAllCheckbox.setAttribute('id', getCheckboxId()); th.innerHTML = ''; th.insertBefore(checkAllCheckbox, null); - window.utils.setup('checkboxRadio', checkAllCheckbox); + utilInstances.push(window.utils.setup('checkboxRadio', checkAllCheckbox)); checkAllCheckbox.addEventListener('input', onCheckAllCheckboxInput); setupCheckboxListeners(); @@ -113,6 +115,16 @@ }); } + function destroy() { + utilInstances.forEach(function(util) { + util.destroy(); + }); + } + init(); + + return { + destroy: destroy, + }; }; })(); diff --git a/static/js/utils/form.js b/static/js/utils/form.js index a8d987c80..0c919ee8f 100644 --- a/static/js/utils/form.js +++ b/static/js/utils/form.js @@ -25,25 +25,37 @@ return false; } + var utilInstances = []; + // reactive buttons - window.utils.setup('reactiveButton', form); + utilInstances.push(window.utils.setup('reactiveButton', form)); // conditonal fieldsets var fieldSets = Array.from(form.querySelectorAll('fieldset[data-conditional-id][data-conditional-value]')); - window.utils.setup('interactiveFieldset', form, { fieldSets }); + utilInstances.push(window.utils.setup('interactiveFieldset', form, { fieldSets })); // hide autoSubmit submit button - window.utils.setup('autoSubmit', form, options); + utilInstances.push(window.utils.setup('autoSubmit', form, options)); // async form if (AJAX_SUBMIT_FLAG in form.dataset) { - window.utils.setup('asyncForm', form, options); + utilInstances.push(window.utils.setup('asyncForm', form, options)); } // inputs - window.utils.setup('inputs', form, options); + utilInstances.push(window.utils.setup('inputs', form, options)); form.classList.add(JS_INITIALIZED); + + function destroyUtils() { + utilInstances.forEach(function(utilInstance) { + utilInstance.destroy(); + }); + } + + return { + destroy: destroyUtils, + }; }; // registers input-listener for each element in (array) and @@ -83,6 +95,10 @@ button.setAttribute('disabled', 'true'); } } + + return { + destroy: function() {}, + }; }; window.utils.interactiveFieldset = function(form, options) { @@ -120,6 +136,10 @@ addEventListeners(); updateFields(); } + + return { + destroy: function() {}, + }; }; window.utils.autoSubmit = function(form, options) { @@ -127,5 +147,9 @@ if (button) { button.classList.add('hidden'); } + + return { + destroy: function() {}, + }; }; })(); diff --git a/static/js/utils/inputs.js b/static/js/utils/inputs.js index 68edee519..26970f0e2 100644 --- a/static/js/utils/inputs.js +++ b/static/js/utils/inputs.js @@ -10,22 +10,36 @@ } window.utils.inputs = function(wrapper, options) { + + var utilInstances = []; + // checkboxes / radios var checkboxes = Array.from(wrapper.querySelectorAll('input[type="checkbox"], input[type="radio"]')); - checkboxes.filter(isNotInitialized).forEach(window.utils.checkboxRadio); + checkboxes.filter(isNotInitialized).forEach(function(checkbox) { + utilInstances.push(window.utils.setup('checkboxRadio', checkbox)); + }); // file-uploads var fileUploads = Array.from(wrapper.querySelectorAll('input[type="file"]')); fileUploads.filter(isNotInitialized).forEach(function(input) { - window.utils.fileUpload(input, options); + utilInstances.push(window.utils.setup('fileUpload', input, options)); }); // file-checkboxes var fileCheckboxes = Array.from(wrapper.querySelectorAll('.file-checkbox')); - fileCheckboxes.filter(isNotInitialized).forEach(function(inp) { - window.utils.fileCheckbox(inp); - inp.classList.add(JS_INITIALIZED_CLASS); + fileCheckboxes.filter(isNotInitialized).forEach(function(input) { + utilInstances.push(window.utils.setup('fileCheckbox', input, options)); }); + + function destroyUtils() { + utilInstances.forEach(function(utilInstance) { + utilInstance.destroy(); + }); + } + + return { + destroy: destroyUtils, + }; }; // (multiple) dynamic file uploads @@ -113,6 +127,10 @@ updateLabel(input.files); }); + + return { + destroy: function() {}, + }; } // to remove previously uploaded files @@ -141,7 +159,12 @@ input.classList.add(JS_INITIALIZED_CLASS); cont.classList.add(JS_INITIALIZED_CLASS); } + setup(); + + return { + destroy: function() {}, + }; } // turns native checkboxes and radio buttons into custom ones @@ -166,6 +189,10 @@ parentEl.appendChild(wrapperEl); } } + + return { + destroy: function() {}, + }; } })(); diff --git a/static/js/utils/modal.js b/static/js/utils/modal.js index 643704e45..4273c5d4d 100644 --- a/static/js/utils/modal.js +++ b/static/js/utils/modal.js @@ -23,6 +23,8 @@ return; } + var utilInstances = []; + var overlayElement = document.createElement('div'); var closerElement = document.createElement('div'); var triggerElement = document.querySelector('#' + modalElement.dataset.trigger); @@ -73,7 +75,7 @@ function setupForm() { var form = modalElement.querySelector('form'); if (form) { - window.utils.setup('form', form, { headers: MODAL_HEADERS }); + utilInstances.push(window.utils.setup('form', form, { headers: MODAL_HEADERS })); } } @@ -136,5 +138,15 @@ } setup(); + + function destroyUtils() { + utilInstances.forEach(function(utilInstance) { + utilInstance.destroy(); + }); + } + + return { + destroy: destroyUtils, + }; }; })(); diff --git a/static/js/utils/showHide.js b/static/js/utils/showHide.js index 5b531c2f1..a2bb2edc0 100644 --- a/static/js/utils/showHide.js +++ b/static/js/utils/showHide.js @@ -51,5 +51,9 @@ }); addEventHandler(el); }); + + return { + destroy: function() {}, + }; }; })(); diff --git a/static/js/utils/tabber.js b/static/js/utils/tabber.js index 38fb43578..e0dd952fe 100644 --- a/static/js/utils/tabber.js +++ b/static/js/utils/tabber.js @@ -86,5 +86,9 @@ $(t).tabgroup(); }); } + + return { + destroy: function() {}, + }; }); })($); From 8317748442a7777153fe8a6c45aabacf638bbb19 Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Sun, 3 Mar 2019 14:55:39 +0100 Subject: [PATCH 3/4] integrate destroy functions from js utilities in setup utility --- static/js/utils/setup.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/static/js/utils/setup.js b/static/js/utils/setup.js index 36e61a9b7..6ed7c4a35 100644 --- a/static/js/utils/setup.js +++ b/static/js/utils/setup.js @@ -14,6 +14,8 @@ window.utils.setup = function(utilName, scope, options) { + var utilInstance; + if (!utilName || !scope) { return; } @@ -27,21 +29,21 @@ } if (options.setupFunction) { - options.setupFunction(scope, options); + utilInstance = options.setupFunction(scope, options); } else { var util = window.utils[utilName]; if (!util) { throw new Error('"' + utilName + '" is not a known js util'); } - util(scope, options); + utilInstance = util(scope, options); } }; + window.utils.teardown(utilName); if (registeredSetupListeners[utilName] && !options.singleton) { registeredSetupListeners[utilName].push(listener); } else { - window.utils.teardown(utilName); registeredSetupListeners[utilName] = [ listener ]; } @@ -52,6 +54,8 @@ bubbles: true, cancelable: true, })); + + return utilInstance; }; window.utils.teardown = function(utilName) { From 5765d3eba079511c47f0c2aa8fa941c664d96c2e Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Sun, 3 Mar 2019 14:55:54 +0100 Subject: [PATCH 4/4] smoother fade out for async contents --- static/css/utils/asyncForm.scss | 2 +- static/css/utils/asyncTable.scss | 2 +- static/css/utils/asyncTableFilter.scss | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/static/css/utils/asyncForm.scss b/static/css/utils/asyncForm.scss index cd4fe3159..4241d8f31 100644 --- a/static/css/utils/asyncForm.scss +++ b/static/css/utils/asyncForm.scss @@ -4,6 +4,6 @@ .async-form-loading { opacity: 0.1; - transition: opacity 800ms ease-in-out; + transition: opacity 800ms ease-out; pointer-events: none; } diff --git a/static/css/utils/asyncTable.scss b/static/css/utils/asyncTable.scss index 0fa11debe..9766daa84 100644 --- a/static/css/utils/asyncTable.scss +++ b/static/css/utils/asyncTable.scss @@ -1,5 +1,5 @@ .async-table--loading { opacity: 0.7; pointer-events: none; - transition: opacity 400ms ease-in-out; + transition: opacity 400ms ease-out; } diff --git a/static/css/utils/asyncTableFilter.scss b/static/css/utils/asyncTableFilter.scss index 3b21ae929..794240f3f 100644 --- a/static/css/utils/asyncTableFilter.scss +++ b/static/css/utils/asyncTableFilter.scss @@ -1,5 +1,5 @@ .async-table-filter--loading { opacity: 0.7; pointer-events: none; - transition: opacity 400ms ease-in-out; + transition: opacity 400ms ease-out; }