diff --git a/frontend/src/utils/async-table/async-table.js b/frontend/src/utils/async-table/async-table.js index 3c8d114e1..e40ecc86d 100644 --- a/frontend/src/utils/async-table/async-table.js +++ b/frontend/src/utils/async-table/async-table.js @@ -3,12 +3,14 @@ import { StorageManager, LOCATION } from '../../lib/storage-manager/storage-mana import { Datepicker } from '../form/datepicker'; import { HttpClient } from '../../services/http-client/http-client'; import * as debounce from 'lodash.debounce'; +import * as throttle from 'lodash.throttle'; import './async-table-filter.sass'; import './async-table.sass'; const ATTR_SUBMIT_LOCKED = 'submit-locked'; const INPUT_DEBOUNCE = 600; +const FILTER_DEBOUNCE = 100; const HEADER_HEIGHT = 80; const ASYNC_TABLE_LOCAL_STORAGE_KEY = 'ASYNC_TABLE'; @@ -35,6 +37,8 @@ export class AsyncTable { _scrollTable; _cssIdPrefix = ''; + _cancelPendingUpdates = []; + _tableFilterInputs = { search: [], input: [], @@ -60,7 +64,7 @@ export class AsyncTable { if (this._element.classList.contains(ASYNC_TABLE_INITIALIZED_CLASS)) { return false; } - + // param asyncTableDbHeader if (this._element.dataset.asyncTableDbHeader !== undefined) { this._asyncTableHeader = this._element.dataset.asyncTableDbHeader; @@ -180,56 +184,62 @@ export class AsyncTable { } _addTableFilterEventListeners(tableFilterForm) { + const debouncedUpdateFromTableFilter = throttle((() => this._updateFromTableFilter(tableFilterForm)).bind(this), FILTER_DEBOUNCE, { leading: true, trailing: false }); + [...this._tableFilterInputs.search, ...this._tableFilterInputs.input].forEach((input) => { - input.submitLockObserver = new MutationObserver((mutations, observer) => { + 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.oldValue === 'true' && input.getAttribute(ATTR_SUBMIT_LOCKED) === 'false') { - this._updateFromTableFilter(tableFilterForm); + 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(); }); const debouncedInput = debounce(() => { const submitLockedAttr = input.getAttribute(ATTR_SUBMIT_LOCKED); - const submitLocked = submitLockedAttr === 'true' || submitLockedAttr === null; + const submitLocked = submitLockedAttr === 'true'; if (!submitLocked && (input.value.length === 0 || input.value.length > 2)) { - this._updateFromTableFilter(tableFilterForm); - } else if (submitLocked) { + debouncedUpdateFromTableFilter(); + } else if (submitLockedAttr === 'true') { // observe the submit lock of the input element - input.submitLockObserver.observe(input, { + submitLockObserver.observe(input, { attributes: true, attributeFilter: [ATTR_SUBMIT_LOCKED], attributeOldValue: true, }); } }, INPUT_DEBOUNCE); + this._cancelPendingUpdates.push(debouncedInput.cancel); - input.addEventListener('input', debouncedInput); input.addEventListener('input', () => { - // set flag to ignore any currently pending requests (not debounced) this._ignoreRequest = true; + debouncedInput(); }); }); this._tableFilterInputs.change.forEach((input) => { input.addEventListener('change', () => { //if (this._element.classList.contains(ASYNC_TABLE_LOADING_CLASS)) - this._updateFromTableFilter(tableFilterForm); + this._ignoreRequest = true; + debouncedUpdateFromTableFilter(); }); }); this._tableFilterInputs.select.forEach((input) => { input.addEventListener('change', () => { - this._updateFromTableFilter(tableFilterForm); + this._ignoreRequest = true; + debouncedUpdateFromTableFilter(); }); }); tableFilterForm.addEventListener('submit', (event) =>{ event.preventDefault(); - this._updateFromTableFilter(tableFilterForm); + this._ignoreRequest = true; + debouncedUpdateFromTableFilter(); }); } @@ -254,7 +264,6 @@ export class AsyncTable { } }; } - this._ignoreRequest = false; this._updateTableFrom(url, callback && callback.bind(this)); } @@ -339,6 +348,12 @@ export class AsyncTable { // fetches new sorted element from url with params and replaces contents of current element _updateTableFrom(url, callback) { + const cancelPendingUpdates = (() => { + this._cancelPendingUpdates.forEach(f => f()); + }).bind(this); + [0, INPUT_DEBOUNCE * 1.5, FILTER_DEBOUNCE * 1.5].forEach(delay => window.setTimeout(cancelPendingUpdates, delay)); + this._ignoreRequest = false; + this._element.classList.add(ASYNC_TABLE_LOADING_CLASS); const headers = { diff --git a/package-lock.json b/package-lock.json index 39368eea4..772738a45 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5367,17 +5367,6 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, - "color-parse": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/color-parse/-/color-parse-1.3.8.tgz", - "integrity": "sha512-1Y79qFv0n1xair3lNMTNeoFvmc3nirMVBij24zbs1f13+7fPpQClMg5b4AuKXLt3szj7BRlHMCXHplkce6XlmA==", - "dev": true, - "requires": { - "color-name": "^1.0.0", - "defined": "^1.0.0", - "is-plain-obj": "^1.1.0" - } - }, "color-string": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", @@ -7228,12 +7217,6 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, - "deepmerge": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz", - "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==", - "dev": true - }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -7284,12 +7267,6 @@ } } }, - "defined": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", - "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", - "dev": true - }, "del": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", @@ -11431,12 +11408,6 @@ "integrity": "sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA==", "dev": true }, - "lodash.isempty": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", - "integrity": "sha1-b4bL7di+TsmHvpqvM8loTbGzHn4=", - "dev": true - }, "lodash.ismatch": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", @@ -11468,6 +11439,11 @@ "lodash._reinterpolate": "~3.0.0" } }, + "lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=" + }, "lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", @@ -11716,15 +11692,6 @@ } } }, - "merge-options": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-1.0.1.tgz", - "integrity": "sha512-iuPV41VWKWBIOpBsjoxjDZw8/GbSfZ2mk7N1453bwMrfzdrIk7EzBd+8UVR6rkw67th7xnk9Dytl3J+lHPdxvg==", - "dev": true, - "requires": { - "is-plain-obj": "^1.1" - } - }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -16405,15 +16372,6 @@ "postcss": "^7.0.2" } }, - "postcss-helpers": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/postcss-helpers/-/postcss-helpers-0.3.2.tgz", - "integrity": "sha512-hppnMXY6Ehe8CgLHQCDWbyUsXvBFggdzftWzznL65MhgZsE8o8pUTYbmUbLst0rps+wyUSLIUJ0bGpV2Tzv7lw==", - "dev": true, - "requires": { - "urijs": "^1.18.12" - } - }, "postcss-image-set-function": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-3.0.1.tgz", @@ -17016,88 +16974,6 @@ "uniq": "^1.0.1" } }, - "posthtml": { - "version": "0.11.6", - "resolved": "https://registry.npmjs.org/posthtml/-/posthtml-0.11.6.tgz", - "integrity": "sha512-C2hrAPzmRdpuL3iH0TDdQ6XCc9M7Dcc3zEW5BLerY65G4tWWszwv6nG/ksi6ul5i2mx22ubdljgktXCtNkydkw==", - "dev": true, - "requires": { - "posthtml-parser": "^0.4.1", - "posthtml-render": "^1.1.5" - } - }, - "posthtml-match-helper": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/posthtml-match-helper/-/posthtml-match-helper-1.0.1.tgz", - "integrity": "sha1-RRJT3o5YRKNI6WOtXt13aesSlRM=", - "dev": true - }, - "posthtml-parser": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.4.2.tgz", - "integrity": "sha512-BUIorsYJTvS9UhXxPTzupIztOMVNPa/HtAm9KHni9z6qEfiJ1bpOBL5DfUOL9XAc3XkLIEzBzpph+Zbm4AdRAg==", - "dev": true, - "requires": { - "htmlparser2": "^3.9.2" - } - }, - "posthtml-render": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/posthtml-render/-/posthtml-render-1.1.5.tgz", - "integrity": "sha512-yvt54j0zCBHQVEFAuR+yHld8CZrCa/E1Z/OcFNCV1IEWTLVxT8O7nYnM4IIw1CD4r8kaRd3lc42+0lgCKgm87w==", - "dev": true - }, - "posthtml-transform": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/posthtml-transform/-/posthtml-transform-1.0.7.tgz", - "integrity": "sha512-ihbO3C1pqjgq94PjgxWyN/S0B+rW3lW3C2o+U0MHYilxDy7zft+u9r+Gk9YT+Nh5DyZyLgWj6Q9H1CR0Z/TPTg==", - "dev": true, - "requires": { - "color-parse": "^1.3.7", - "loader-utils": "^1.1.0", - "lodash": "^4.17.14", - "postcss-values-parser": "^1.5.0", - "posthtml": "^0.11.3", - "posthtml-match-helper": "^1.0.1", - "slashes": "^1.0.5", - "unquote": "^1.1.1" - }, - "dependencies": { - "postcss-values-parser": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-1.5.0.tgz", - "integrity": "sha512-3M3p+2gMp0AH3da530TlX8kiO1nxdTnc3C6vr8dMxRLIlh8UYkz0/wcwptSXjhtx2Fr0TySI7a+BHDQ8NL7LaQ==", - "dev": true, - "requires": { - "flatten": "^1.0.2", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - } - } - }, - "postsvg": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/postsvg/-/postsvg-2.2.7.tgz", - "integrity": "sha512-TyRnoyEvszrEGOzxaTycnUgJZ0W2Xnd9fOmgfuy61Qjo6JhDPhAIBQ1dspQCvdVpK9KkIlZkSETSjmbO0gVIag==", - "dev": true, - "requires": { - "clone": "^1.0.4", - "deepmerge": "^2.1.0", - "posthtml": "^0.11.3", - "posthtml-match-helper": "^1.0.1", - "posthtml-parser": "^0.4.1", - "posthtml-render": "^1.1.2" - }, - "dependencies": { - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true - } - } - }, "prebuild-install": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.3.tgz", @@ -18295,12 +18171,6 @@ "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", "dev": true }, - "slashes": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/slashes/-/slashes-1.0.5.tgz", - "integrity": "sha1-IEeY9sYyAU1gm12JtqV6eq9wgo0=", - "dev": true - }, "slice-ansi": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", @@ -18613,12 +18483,6 @@ "through": "2" } }, - "split-on-first": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", - "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", - "dev": true - }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -19224,54 +19088,6 @@ "has-flag": "^3.0.0" } }, - "svg-mixer-utils": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/svg-mixer-utils/-/svg-mixer-utils-0.3.4.tgz", - "integrity": "sha512-szkeG+Jn6DRo7QlnUOYKslm4J6dg37I8E+tLG1PB13U6UhSlkC8teCisyT7ZRGRwTcTxmNizvu+/oe8uJUf5EA==", - "dev": true, - "requires": { - "ajv": "^6.5.1", - "anymatch": "^2.0.0", - "memory-fs": "^0.4.1", - "merge-options": "^1.0.0", - "postcss-helpers": "^0.3.2" - } - }, - "svg-transform-loader": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/svg-transform-loader/-/svg-transform-loader-2.0.8.tgz", - "integrity": "sha512-cK9PXVGV+hZoiR7lkIir+LOZ2qmJIuq0tm/emv5x7MF4PvyqY/lCNlYuctx37rSNgGE6Lhpdn8lUCZm8d/oNAQ==", - "dev": true, - "requires": { - "loader-utils": "^1.1.0", - "lodash.isempty": "^4.4.0", - "merge-options": "^1.0.0", - "postcss": "^7.0.14", - "posthtml-transform": "^1.0.7", - "postsvg": "^2.2.7", - "query-string": "^6.1.0", - "svg-mixer-utils": "^0.3.4" - }, - "dependencies": { - "query-string": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.9.0.tgz", - "integrity": "sha512-KG4bhCFYapExLsUHrFt+kQVEegF2agm4cpF/VNc6pZVthIfCc/GK8t8VyNIE3nyXG9DK3Tf2EGkxjR6/uRdYsA==", - "dev": true, - "requires": { - "decode-uri-component": "^0.2.0", - "split-on-first": "^1.0.0", - "strict-uri-encode": "^2.0.0" - } - }, - "strict-uri-encode": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", - "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=", - "dev": true - } - } - }, "svgo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", @@ -20025,12 +19841,6 @@ "punycode": "^2.1.0" } }, - "urijs": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.2.tgz", - "integrity": "sha512-s/UIq9ap4JPZ7H1EB5ULo/aOUbWqfDi7FKzMC2Nz+0Si8GiT1rIEaprt8hy3Vy2Ex2aJPpOQv4P4DuOZ+K1c6w==", - "dev": true - }, "urix": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", diff --git a/package.json b/package.json index e85cd6f14..6bea4e881 100644 --- a/package.json +++ b/package.json @@ -111,6 +111,7 @@ "@babel/runtime": "^7.7.6", "@juggle/resize-observer": "^2.5.0", "core-js": "^3.4.8", + "lodash.throttle": "^4.1.1", "moment": "^2.24.0", "npm": "^6.13.4", "tail.datetime": "git+ssh://git@gitlab2.rz.ifi.lmu.de/uni2work/tail.DateTime.git#master", diff --git a/webpack.config.js b/webpack.config.js index 93d802122..3cc5b8379 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -19,51 +19,51 @@ module.exports = { module: { rules: [ { - loader: 'babel-loader', + loader: 'babel-loader', - options: { - plugins: ['syntax-dynamic-import'], + options: { + plugins: ['syntax-dynamic-import'], - presets: [ - [ - '@babel/preset-env', - { - modules: false, - targets: { - edge: "17", - firefox: "50", - chrome: "60", - safari: "11.1", - ie: "11", - }, - useBuiltIns: "usage", - corejs: 3 - } - ] - ] - }, - test: /\.js$/i, - exclude: /node_modules/, + presets: [ + [ + '@babel/preset-env', + { + modules: false, + targets: { + edge: "17", + firefox: "50", + chrome: "60", + safari: "11.1", + ie: "11", + }, + useBuiltIns: "usage", + corejs: 3 + } + ] + ] + }, + test: /\.js$/i, + exclude: /node_modules/, }, { test: /\.css$/i, use: [ MiniCssExtractPlugin.loader, { loader: 'css-loader', options: { sourceMap: true }}, { loader: 'postcss-loader', options: { - sourceMap: true, - plugins: () => [ postcssPresetEnv ] - }}, + sourceMap: true, + plugins: () => [ postcssPresetEnv ] + }}, { loader: 'resolve-url-loader', options: { sourceMap: true }} ] }, { - test: /\.s(c|a)ss$/i, + test: /\.s(c|a)ss$/i, use: [ MiniCssExtractPlugin.loader, { loader: 'css-loader', options: { sourceMap: true }}, { loader: 'postcss-loader', options: { - sourceMap: true, - plugins: () => [ postcssPresetEnv ] - }}, + sourceMap: true, + plugins: () => [ postcssPresetEnv ] + }}, { loader: 'resolve-url-loader', options: { sourceMap: true }}, { loader: 'sass-loader', options: { implementation: require('sass'), sourceMap: true }} ] @@ -134,27 +134,27 @@ module.exports = { statsFilename: path.resolve(__dirname, 'config/favicon.json'), persistentCache: false, config: { - background: '#fff', - icons: { - android: false, - appleIcon: false, - appleStartup: false, - coast: false, - favicons: true, - firefox: false, - windows: false, - yandex: false - } + background: '#fff', + icons: { + android: false, + appleIcon: false, + appleStartup: false, + coast: false, + favicons: true, + firefox: false, + windows: false, + yandex: false + } } }), new RemovePlugin({ after: { - test: [ - { folder: path.resolve(__dirname, `static/wp-${webpackVersion}`), - method: (filePath) => { return new RegExp(/\/.*-icons\/.*\.(xml|json|webapp)$/, 'm').test(filePath); }, - recursive: true - } - ] + test: [ + { folder: path.resolve(__dirname, `static/wp-${webpackVersion}`), + method: (filePath) => { return new RegExp(/\/.*-icons\/.*\.(xml|json|webapp)$/, 'm').test(filePath); }, + recursive: true + } + ] } }) ], @@ -177,7 +177,7 @@ module.exports = { sourceMap: true }), new OptimizeCSSAssetsPlugin({ - cssProcessorOptions: { + cssProcessorOptions: { map: { inline: false }