parent
9e47a7cd28
commit
c348b7cb03
@ -9,9 +9,10 @@ export const LOCATION = {
|
|||||||
LOCAL: 'local',
|
LOCAL: 'local',
|
||||||
SESSION: 'session',
|
SESSION: 'session',
|
||||||
WINDOW: 'window',
|
WINDOW: 'window',
|
||||||
|
HISTORY: 'history',
|
||||||
};
|
};
|
||||||
|
|
||||||
const LOCATION_SHADOWING = [ LOCATION.WINDOW, LOCATION.SESSION, LOCATION.LOCAL ];
|
const LOCATION_SHADOWING = [ LOCATION.HISTORY, LOCATION.WINDOW, LOCATION.SESSION, LOCATION.LOCAL ];
|
||||||
|
|
||||||
export class StorageManager {
|
export class StorageManager {
|
||||||
|
|
||||||
@ -26,7 +27,16 @@ export class StorageManager {
|
|||||||
constructor(namespace, version, options) {
|
constructor(namespace, version, options) {
|
||||||
this._debugLog('constructor', namespace, version, options);
|
this._debugLog('constructor', namespace, version, options);
|
||||||
|
|
||||||
this.namespace = namespace;
|
if (typeof namespace === 'object') {
|
||||||
|
let sep = '_';
|
||||||
|
const namespace_arr = Array.from(namespace);
|
||||||
|
while (namespace_arr.some(str => str.includes(sep)))
|
||||||
|
sep = sep + '_';
|
||||||
|
|
||||||
|
this.namespace = Array.from(namespace).join(sep);
|
||||||
|
} else {
|
||||||
|
this.namespace = namespace;
|
||||||
|
}
|
||||||
this.version = semver.valid(version);
|
this.version = semver.valid(version);
|
||||||
|
|
||||||
if (!namespace) {
|
if (!namespace) {
|
||||||
@ -48,7 +58,7 @@ export class StorageManager {
|
|||||||
throw new Error('Cannot setup StorageManager without window or global');
|
throw new Error('Cannot setup StorageManager without window or global');
|
||||||
|
|
||||||
if (this._options.encryption) {
|
if (this._options.encryption) {
|
||||||
[LOCATION.LOCAL, LOCATION.SESSION].forEach((location) => {
|
[LOCATION.LOCAL, LOCATION.SESSION, LOCATION.HISTORY].forEach((location) => {
|
||||||
const encryption = this._options.encryption.all || this._options.encryption[location];
|
const encryption = this._options.encryption.all || this._options.encryption[location];
|
||||||
if (encryption) this._requestStorageKey({ location: location, encryption: encryption });
|
if (encryption) this._requestStorageKey({ location: location, encryption: encryption });
|
||||||
});
|
});
|
||||||
@ -70,17 +80,21 @@ export class StorageManager {
|
|||||||
|
|
||||||
switch (location) {
|
switch (location) {
|
||||||
case LOCATION.LOCAL: {
|
case LOCATION.LOCAL: {
|
||||||
this._saveToLocalStorage(this._updateStorage(this._getFromLocalStorage(options), { [key]: value }, LOCATION.LOCAL, options));
|
this._saveToLocalStorage({ ...this._getFromLocalStorage(options), [key]: value}, options);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case LOCATION.SESSION: {
|
case LOCATION.SESSION: {
|
||||||
this._saveToSessionStorage(this._updateStorage(this._getFromSessionStorage(options), { [key]: value }, LOCATION.SESSION, options));
|
this._saveToSessionStorage({ ...this._getFromSessionStorage(options), [key]: value}, options);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case LOCATION.WINDOW: {
|
case LOCATION.WINDOW: {
|
||||||
this._saveToWindow({ ...this._getFromWindow(), [key]: value });
|
this._saveToWindow({ ...this._getFromWindow(), [key]: value });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case LOCATION.HISTORY: {
|
||||||
|
this._saveToHistory({ ...this._getFromHistory(), [key]: value }, options);
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
console.error('StorageManager.save cannot save item with unsupported location');
|
console.error('StorageManager.save cannot save item with unsupported location');
|
||||||
}
|
}
|
||||||
@ -112,6 +126,10 @@ export class StorageManager {
|
|||||||
val = this._getFromWindow()[key];
|
val = this._getFromWindow()[key];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case LOCATION.HISTORY: {
|
||||||
|
val = this._getFromHistory(options)[key];
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
console.error('StorageManager.load cannot load item with unsupported location');
|
console.error('StorageManager.load cannot load item with unsupported location');
|
||||||
}
|
}
|
||||||
@ -138,14 +156,14 @@ export class StorageManager {
|
|||||||
|
|
||||||
delete val[key];
|
delete val[key];
|
||||||
|
|
||||||
return this._saveToLocalStorage(val);
|
return this._saveToLocalStorage(val, options);
|
||||||
}
|
}
|
||||||
case LOCATION.SESSION: {
|
case LOCATION.SESSION: {
|
||||||
let val = this._getFromSessionStorage(options);
|
let val = this._getFromSessionStorage(options);
|
||||||
|
|
||||||
delete val[key];
|
delete val[key];
|
||||||
|
|
||||||
return this._saveToSessionStorage(val);
|
return this._saveToSessionStorage(val, options);
|
||||||
}
|
}
|
||||||
case LOCATION.WINDOW: {
|
case LOCATION.WINDOW: {
|
||||||
let val = this._getFromWindow();
|
let val = this._getFromWindow();
|
||||||
@ -154,6 +172,14 @@ export class StorageManager {
|
|||||||
|
|
||||||
return this._saveToWindow(val);
|
return this._saveToWindow(val);
|
||||||
}
|
}
|
||||||
|
case LOCATION.HISTORY: {
|
||||||
|
let val = this._getFromHistory(options);
|
||||||
|
|
||||||
|
delete val[key];
|
||||||
|
|
||||||
|
return this._saveToHistory(val, options);
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
console.error('StorageManager.load cannot load item with unsupported location');
|
console.error('StorageManager.load cannot load item with unsupported location');
|
||||||
}
|
}
|
||||||
@ -177,6 +203,8 @@ export class StorageManager {
|
|||||||
return this._clearSessionStorage();
|
return this._clearSessionStorage();
|
||||||
case LOCATION.WINDOW:
|
case LOCATION.WINDOW:
|
||||||
return this._clearWindow();
|
return this._clearWindow();
|
||||||
|
case LOCATION.HISTORY:
|
||||||
|
return this._clearHistory(options && options.history);
|
||||||
default:
|
default:
|
||||||
console.error('StorageManager.clear cannot clear with unsupported location');
|
console.error('StorageManager.clear cannot clear with unsupported location');
|
||||||
}
|
}
|
||||||
@ -185,7 +213,7 @@ export class StorageManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
_getFromLocalStorage(options=this._options) {
|
_getFromLocalStorage(options) {
|
||||||
this._debugLog('_getFromLocalStorage', options);
|
this._debugLog('_getFromLocalStorage', options);
|
||||||
|
|
||||||
let state;
|
let state;
|
||||||
@ -210,7 +238,7 @@ export class StorageManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_saveToLocalStorage(state) {
|
_saveToLocalStorage(state, options) {
|
||||||
this._debugLog('_saveToLocalStorage', state);
|
this._debugLog('_saveToLocalStorage', state);
|
||||||
|
|
||||||
if (!state)
|
if (!state)
|
||||||
@ -224,7 +252,7 @@ export class StorageManager {
|
|||||||
versionedState = { version: this.version, ...state };
|
versionedState = { version: this.version, ...state };
|
||||||
}
|
}
|
||||||
|
|
||||||
window.localStorage.setItem(this.namespace, JSON.stringify(versionedState));
|
window.localStorage.setItem(this.namespace, JSON.stringify(this._updateStorage({}, versionedState, LOCATION.LOCAL, options)));
|
||||||
}
|
}
|
||||||
|
|
||||||
_clearLocalStorage() {
|
_clearLocalStorage() {
|
||||||
@ -240,10 +268,10 @@ export class StorageManager {
|
|||||||
if (!this._global || !this._global.App)
|
if (!this._global || !this._global.App)
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
if (!this._global.App.Storage)
|
if (!this._global.App.Storage || !this._global.App.Storage[this.namespace])
|
||||||
this._global.App.Storage = {};
|
return {};
|
||||||
|
|
||||||
return this._global.App.Storage;
|
return this._global.App.Storage[this.namespace];
|
||||||
}
|
}
|
||||||
|
|
||||||
_saveToWindow(value) {
|
_saveToWindow(value) {
|
||||||
@ -274,8 +302,71 @@ export class StorageManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_getFromHistory(options) {
|
||||||
|
this._debugLog('_getFromHistory');
|
||||||
|
|
||||||
_getFromSessionStorage(options=this._options) {
|
if (!this._global || !this._global.history)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
if (!this._global.history.state || !this._global.history.state[this.namespace])
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return this._getFromStorage(this._global.history.state[this.namespace], LOCATION.HISTORY, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
_saveToHistory(value, options) {
|
||||||
|
this._debugLog('_saveToHistory', options);
|
||||||
|
|
||||||
|
|
||||||
|
if (!this._global || !this._global.history) {
|
||||||
|
throw new Error('StorageManager._saveToHistory called when window.history is not available');
|
||||||
|
}
|
||||||
|
|
||||||
|
const push = (options.history && typeof options.history.push !== 'undefined') ? !!options.history.push : true;
|
||||||
|
const title = (options.history && options.history.title) || (this._global.document && this._global.document.title) || '';
|
||||||
|
const url = (options.history && options.history.url) || (this._global.document && this._global.document.location);
|
||||||
|
|
||||||
|
const state = this._global.history.state || {};
|
||||||
|
state[this.namespace] = this._updateStorage({}, value, LOCATION.HISTORY, options);
|
||||||
|
|
||||||
|
this._debugLog('_saveToHistory', { state: state, push: push, title: title, url: url});
|
||||||
|
|
||||||
|
if (push)
|
||||||
|
this._global.history.pushState(state, title, url);
|
||||||
|
else
|
||||||
|
this._global.history.replaceState(state, title, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
_clearHistory(options) {
|
||||||
|
this._debugLog('_clearHistory', options);
|
||||||
|
|
||||||
|
if (!this._global || !this._global.history) {
|
||||||
|
throw new Error('StorageManager._clearHistory called when window.history is not available');
|
||||||
|
}
|
||||||
|
|
||||||
|
const push = (options.history && typeof options.history.push !== 'undefined' ? !!options.history.push : true) || true;
|
||||||
|
const title = (options.history && options.history.title) || (this._global.document && this._global.document.title) || '';
|
||||||
|
const url = (options.history && options.history.url) || (this._global.document && this._global.document.location);
|
||||||
|
|
||||||
|
const state = this._global.history.state || {};
|
||||||
|
delete state[this.namespace];
|
||||||
|
|
||||||
|
if (push)
|
||||||
|
this._global.history.pushState(state, title, url);
|
||||||
|
else
|
||||||
|
this._global.history.replaceState(state, title, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
addHistoryListener(listener, options=this._options, ...args) {
|
||||||
|
const modified_listener = (function(event, ...listener_args) { // eslint-disable-line no-unused-vars
|
||||||
|
this._global.setTimeout(() => listener(this._getFromHistory(options), ...listener_args));
|
||||||
|
}).bind(this);
|
||||||
|
|
||||||
|
this._global.addEventListener('popstate', modified_listener, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_getFromSessionStorage(options) {
|
||||||
this._debugLog('_getFromSessionStorage', options);
|
this._debugLog('_getFromSessionStorage', options);
|
||||||
|
|
||||||
let state;
|
let state;
|
||||||
@ -300,7 +391,7 @@ export class StorageManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_saveToSessionStorage(state) {
|
_saveToSessionStorage(state, options) {
|
||||||
this._debugLog('_saveToSessionStorage', state);
|
this._debugLog('_saveToSessionStorage', state);
|
||||||
|
|
||||||
if (!state)
|
if (!state)
|
||||||
@ -314,7 +405,7 @@ export class StorageManager {
|
|||||||
versionedState = { version: this.version, ...state };
|
versionedState = { version: this.version, ...state };
|
||||||
}
|
}
|
||||||
|
|
||||||
window.sessionStorage.setItem(this.namespace, JSON.stringify(versionedState));
|
window.sessionStorage.setItem(this.namespace, JSON.stringify(this._updateStorage({}, versionedState, LOCATION.SESSION, options)));
|
||||||
}
|
}
|
||||||
|
|
||||||
_clearSessionStorage() {
|
_clearSessionStorage() {
|
||||||
@ -324,10 +415,10 @@ export class StorageManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
_getFromStorage(storage, location, options=this._options) {
|
_getFromStorage(storage, location, options) {
|
||||||
this._debugLog('_getFromStorage', storage, location, options);
|
this._debugLog('_getFromStorage', storage, location, options);
|
||||||
|
|
||||||
const encryption = options.encryption && (options.encryption.all || options.encryption[location]);
|
const encryption = options && options.encryption && (options.encryption.all || options.encryption[location]);
|
||||||
if (encryption && storage.encryption) {
|
if (encryption && storage.encryption) {
|
||||||
return { ...storage, ...JSON.parse(decrypt(storage.encryption.ciphertext, this._encryptionKey[location]) || null) };
|
return { ...storage, ...JSON.parse(decrypt(storage.encryption.ciphertext, this._encryptionKey[location]) || null) };
|
||||||
} else {
|
} else {
|
||||||
@ -335,10 +426,10 @@ export class StorageManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateStorage(storage, update, location, options=this._options) {
|
_updateStorage(storage, update, location, options) {
|
||||||
this._debugLog('_updateStorage', storage, update, location, options);
|
this._debugLog('_updateStorage', storage, update, location, options);
|
||||||
|
|
||||||
const encryption = options.encryption && (options.encryption.all || options.encryption[location]);
|
const encryption = options && options.encryption && (options.encryption.all || options.encryption[location]);
|
||||||
if (encryption && storage.encryption) {
|
if (encryption && storage.encryption) {
|
||||||
const updatedDecryptedStorage = { ...JSON.parse(decrypt(storage.encryption.ciphertext, this._encryptionKey[location]) || null), ...update };
|
const updatedDecryptedStorage = { ...JSON.parse(decrypt(storage.encryption.ciphertext, this._encryptionKey[location]) || null), ...update };
|
||||||
console.log('updatedDecryptedStorage', updatedDecryptedStorage);
|
console.log('updatedDecryptedStorage', updatedDecryptedStorage);
|
||||||
@ -357,13 +448,13 @@ export class StorageManager {
|
|||||||
const enc = this.load('encryption', { ...options, encryption: false });
|
const enc = this.load('encryption', { ...options, encryption: false });
|
||||||
const requestBody = {
|
const requestBody = {
|
||||||
type : options.encryption,
|
type : options.encryption,
|
||||||
length : 42,
|
length : sodium.crypto_secretbox_KEYBYTES,
|
||||||
salt : enc.salt,
|
salt : enc.salt,
|
||||||
timestamp : enc.timestamp,
|
timestamp : enc.timestamp,
|
||||||
};
|
};
|
||||||
|
|
||||||
this._global.App.httpClient.post({
|
this._global.App.httpClient.post({
|
||||||
url: '../../../../../../user/storage-key', // TODO use APPROOT instead
|
url: '/user/storage-key',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type' : HttpClient.ACCEPT.JSON,
|
'Content-Type' : HttpClient.ACCEPT.JSON,
|
||||||
'Accept' : HttpClient.ACCEPT.JSON,
|
'Accept' : HttpClient.ACCEPT.JSON,
|
||||||
@ -381,11 +472,10 @@ export class StorageManager {
|
|||||||
}).catch(console.error);
|
}).catch(console.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
_debugLog() {
|
_debugLog() {}
|
||||||
// _debugLog(fName, ...args) {
|
// _debugLog(fName, ...args) {
|
||||||
// console.log(`[DEBUGLOG] StorageManager.${fName}`, { args: args, instance: this });
|
// console.log(`[DEBUGLOG] StorageManager.${fName}`, { args: args, instance: this });
|
||||||
}
|
// }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,7 @@ export class HtmlHelpers {
|
|||||||
idPrefix = this._getIdPrefix();
|
idPrefix = this._getIdPrefix();
|
||||||
this._prefixIds(element, idPrefix);
|
this._prefixIds(element, idPrefix);
|
||||||
}
|
}
|
||||||
return Promise.resolve({ idPrefix, element });
|
return Promise.resolve({ idPrefix, element, headers: response.headers });
|
||||||
},
|
},
|
||||||
Promise.reject,
|
Promise.reject,
|
||||||
).catch(console.error);
|
).catch(console.error);
|
||||||
|
|||||||
@ -13,7 +13,9 @@ const INPUT_DEBOUNCE = 600;
|
|||||||
const FILTER_DEBOUNCE = 100;
|
const FILTER_DEBOUNCE = 100;
|
||||||
const HEADER_HEIGHT = 80;
|
const HEADER_HEIGHT = 80;
|
||||||
|
|
||||||
const ASYNC_TABLE_LOCAL_STORAGE_KEY = 'ASYNC_TABLE';
|
const ASYNC_TABLE_STORAGE_KEY = 'ASYNC_TABLE';
|
||||||
|
const ASYNC_TABLE_STORAGE_VERSION = '2.0.0';
|
||||||
|
|
||||||
const ASYNC_TABLE_SCROLLTABLE_SELECTOR = '.scrolltable';
|
const ASYNC_TABLE_SCROLLTABLE_SELECTOR = '.scrolltable';
|
||||||
const ASYNC_TABLE_INITIALIZED_CLASS = 'async-table--initialized';
|
const ASYNC_TABLE_INITIALIZED_CLASS = 'async-table--initialized';
|
||||||
const ASYNC_TABLE_LOADING_CLASS = 'async-table--loading';
|
const ASYNC_TABLE_LOADING_CLASS = 'async-table--loading';
|
||||||
@ -31,6 +33,8 @@ export class AsyncTable {
|
|||||||
_asyncTableHeader;
|
_asyncTableHeader;
|
||||||
_asyncTableId;
|
_asyncTableId;
|
||||||
|
|
||||||
|
_asyncTableIdent;
|
||||||
|
|
||||||
_ths = [];
|
_ths = [];
|
||||||
_pageLinks = [];
|
_pageLinks = [];
|
||||||
_pagesizeForm;
|
_pagesizeForm;
|
||||||
@ -47,7 +51,10 @@ export class AsyncTable {
|
|||||||
};
|
};
|
||||||
_ignoreRequest = false;
|
_ignoreRequest = false;
|
||||||
|
|
||||||
_storageManager = new StorageManager(ASYNC_TABLE_LOCAL_STORAGE_KEY, '1.0.0', { location: LOCATION.WINDOW });
|
_windowStorage;
|
||||||
|
_historyStorage;
|
||||||
|
|
||||||
|
_active = true;
|
||||||
|
|
||||||
constructor(element, app) {
|
constructor(element, app) {
|
||||||
if (!element) {
|
if (!element) {
|
||||||
@ -79,21 +86,58 @@ export class AsyncTable {
|
|||||||
this._cssIdPrefix = findCssIdPrefix(rawTableId);
|
this._cssIdPrefix = findCssIdPrefix(rawTableId);
|
||||||
this._asyncTableId = rawTableId.replace(this._cssIdPrefix, '');
|
this._asyncTableId = rawTableId.replace(this._cssIdPrefix, '');
|
||||||
|
|
||||||
|
this._asyncTableIdent = this._asyncTableId.replace(/-table-wrapper$/, '');
|
||||||
|
|
||||||
|
if (!this._asyncTableIdent) {
|
||||||
|
throw new Error('Async Table cannot be set up without an ident!');
|
||||||
|
}
|
||||||
|
|
||||||
|
this._windowStorage = new StorageManager([ASYNC_TABLE_STORAGE_KEY, this._asyncTableIdent], ASYNC_TABLE_STORAGE_VERSION, { location: LOCATION.WINDOW });
|
||||||
|
this._historyStorage = new StorageManager([ASYNC_TABLE_STORAGE_KEY, this._asyncTableIdent], ASYNC_TABLE_STORAGE_VERSION, { location: LOCATION.HISTORY });
|
||||||
|
|
||||||
// find scrolltable wrapper
|
// find scrolltable wrapper
|
||||||
this._scrollTable = this._element.querySelector(ASYNC_TABLE_SCROLLTABLE_SELECTOR);
|
this._scrollTable = this._element.querySelector(ASYNC_TABLE_SCROLLTABLE_SELECTOR);
|
||||||
if (!this._scrollTable) {
|
if (!this._scrollTable) {
|
||||||
throw new Error('Async Table cannot be set up without a scrolltable element!');
|
throw new Error('Async Table cannot be set up without a scrolltable element!');
|
||||||
}
|
}
|
||||||
|
|
||||||
this._setupTableFilter();
|
|
||||||
|
|
||||||
this._processStorage();
|
this._processStorage();
|
||||||
|
|
||||||
// clear currentTableUrl from previous requests
|
this._setupTableFilter();
|
||||||
this._storageManager.remove('currentTableUrl');
|
|
||||||
|
this._windowStorage.remove('currentTableUrl');
|
||||||
|
|
||||||
|
if (!('currentTableUrl' in this._element.dataset)) {
|
||||||
|
this._element.dataset['currentTableUrl'] = document.location.href;
|
||||||
|
this._historyStorage.save('currentTableUrl', document.location.href, { location: LOCATION.HISTORY, history: { push: false } });
|
||||||
|
}
|
||||||
|
|
||||||
|
this._historyListener();
|
||||||
|
|
||||||
|
this._historyStorage.addHistoryListener(this._historyListener.bind(this));
|
||||||
|
|
||||||
// mark initialized
|
// mark initialized
|
||||||
this._element.classList.add(ASYNC_TABLE_INITIALIZED_CLASS);
|
if (this._active)
|
||||||
|
this._element.classList.add(ASYNC_TABLE_INITIALIZED_CLASS);
|
||||||
|
}
|
||||||
|
|
||||||
|
_historyListener(historyState) {
|
||||||
|
if (!this._active)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const windowUrl = this._element.dataset['currentTableUrl'];
|
||||||
|
const historyUrl = historyState ? historyState['currentTableUrl'] : this._historyStorage.load('currentTableUrl');
|
||||||
|
this._debugLog('_historyListener', historyState, windowUrl, historyUrl);
|
||||||
|
|
||||||
|
if (this._isEquivalentUrl(windowUrl, historyUrl))
|
||||||
|
return;
|
||||||
|
|
||||||
|
this._debugLog('_historyListener', historyUrl);
|
||||||
|
this._updateTableFrom(historyUrl || document.location.href, undefined, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
_isEquivalentUrl(a, b) {
|
||||||
|
return a === b;
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
@ -113,7 +157,7 @@ export class AsyncTable {
|
|||||||
|
|
||||||
this._ths.forEach((th) => {
|
this._ths.forEach((th) => {
|
||||||
th.clickHandler = (event) => {
|
th.clickHandler = (event) => {
|
||||||
this._storageManager.save('horizPos', (this._scrollTable || {}).scrollLeft);
|
this._windowStorage.save('horizPos', (this._scrollTable || {}).scrollLeft);
|
||||||
this._linkClickHandler(event);
|
this._linkClickHandler(event);
|
||||||
};
|
};
|
||||||
th.element.addEventListener('click', th.clickHandler);
|
th.element.addEventListener('click', th.clickHandler);
|
||||||
@ -135,7 +179,7 @@ export class AsyncTable {
|
|||||||
left: this._scrollTable.offsetLeft || 0,
|
left: this._scrollTable.offsetLeft || 0,
|
||||||
behavior: 'smooth',
|
behavior: 'smooth',
|
||||||
};
|
};
|
||||||
this._storageManager.save('scrollTo', scrollTo);
|
this._windowStorage.save('scrollTo', scrollTo);
|
||||||
}
|
}
|
||||||
this._linkClickHandler(event);
|
this._linkClickHandler(event);
|
||||||
};
|
};
|
||||||
@ -256,7 +300,7 @@ export class AsyncTable {
|
|||||||
const prefix = findCssIdPrefix(focusedInput.id);
|
const prefix = findCssIdPrefix(focusedInput.id);
|
||||||
const focusId = focusedInput.id.replace(prefix, '');
|
const focusId = focusedInput.id.replace(prefix, '');
|
||||||
callback = function(wrapper) {
|
callback = function(wrapper) {
|
||||||
const idPrefix = this._storageManager.load('cssIdPrefix');
|
const idPrefix = this._windowStorage.load('cssIdPrefix');
|
||||||
const toBeFocused = wrapper.querySelector('#' + idPrefix + focusId);
|
const toBeFocused = wrapper.querySelector('#' + idPrefix + focusId);
|
||||||
if (toBeFocused) {
|
if (toBeFocused) {
|
||||||
toBeFocused.focus();
|
toBeFocused.focus();
|
||||||
@ -268,34 +312,33 @@ export class AsyncTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_serializeTableFilterToURL(tableFilterForm) {
|
_serializeTableFilterToURL(tableFilterForm) {
|
||||||
const url = new URL(this._storageManager.load('currentTableUrl') || window.location.href);
|
const url = new URL(this._windowStorage.load('currentTableUrl') || window.location.href);
|
||||||
|
|
||||||
// create new FormData and format any date values
|
// create new FormData and format any date values
|
||||||
const formData = Datepicker.unformatAll(tableFilterForm, new FormData(tableFilterForm));
|
const formData = Datepicker.unformatAll(tableFilterForm, new FormData(tableFilterForm));
|
||||||
|
|
||||||
for (var k of url.searchParams.keys()) {
|
this._debugLog('_serializeTableFilterToURL', Array.from(formData.entries()), url.href);
|
||||||
url.searchParams.delete(k);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var kv of formData.entries()) {
|
const searchParams = new URLSearchParams(Array.from(formData.entries()));
|
||||||
url.searchParams.append(kv[0], kv[1]);
|
url.search = searchParams.toString();
|
||||||
}
|
|
||||||
|
this._debugLog('_serializeTableFilterToURL', url.href);
|
||||||
|
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
_processStorage() {
|
_processStorage() {
|
||||||
const scrollTo = this._storageManager.load('scrollTo');
|
const scrollTo = this._windowStorage.load('scrollTo');
|
||||||
if (scrollTo && this._scrollTable) {
|
if (scrollTo && this._scrollTable) {
|
||||||
window.scrollTo(scrollTo);
|
window.scrollTo(scrollTo);
|
||||||
}
|
}
|
||||||
this._storageManager.remove('scrollTo');
|
this._windowStorage.remove('scrollTo');
|
||||||
|
|
||||||
const horizPos = this._storageManager.load('horizPos');
|
const horizPos = this._windowStorage.load('horizPos');
|
||||||
if (horizPos && this._scrollTable) {
|
if (horizPos && this._scrollTable) {
|
||||||
this._scrollTable.scrollLeft = horizPos;
|
this._scrollTable.scrollLeft = horizPos;
|
||||||
}
|
}
|
||||||
this._storageManager.remove('horizPos');
|
this._windowStorage.remove('horizPos');
|
||||||
}
|
}
|
||||||
|
|
||||||
_removeListeners() {
|
_removeListeners() {
|
||||||
@ -330,7 +373,7 @@ export class AsyncTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_changePagesizeHandler = () => {
|
_changePagesizeHandler = () => {
|
||||||
const url = new URL(this._storageManager.load('currentTableUrl') || window.location.href);
|
const url = new URL(this._windowStorage.load('currentTableUrl') || window.location.href);
|
||||||
|
|
||||||
// create new FormData and format any date values
|
// create new FormData and format any date values
|
||||||
const formData = Datepicker.unformatAll(this._pagesizeForm, new FormData(this._pagesizeForm));
|
const formData = Datepicker.unformatAll(this._pagesizeForm, new FormData(this._pagesizeForm));
|
||||||
@ -347,7 +390,9 @@ export class AsyncTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// fetches new sorted element from url with params and replaces contents of current element
|
// fetches new sorted element from url with params and replaces contents of current element
|
||||||
_updateTableFrom(url, callback) {
|
_updateTableFrom(url, callback, isPopState) {
|
||||||
|
url = new URL(url);
|
||||||
|
|
||||||
const cancelPendingUpdates = (() => {
|
const cancelPendingUpdates = (() => {
|
||||||
this._cancelPendingUpdates.forEach(f => f());
|
this._cancelPendingUpdates.forEach(f => f());
|
||||||
}).bind(this);
|
}).bind(this);
|
||||||
@ -372,23 +417,33 @@ export class AsyncTable {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._storageManager.save('currentTableUrl', url.href);
|
if (!isPopState)
|
||||||
|
this._historyStorage.save('currentTableUrl', url.href, { location: LOCATION.HISTORY, history: { push: true, url: response.headers.get('DB-Table-Canonical-URL') || url.href } });
|
||||||
|
|
||||||
|
this._windowStorage.save('currentTableUrl', url.href);
|
||||||
// reset table
|
// reset table
|
||||||
this._removeListeners();
|
this._removeListeners();
|
||||||
|
this._active = false;
|
||||||
this._element.classList.remove(ASYNC_TABLE_INITIALIZED_CLASS);
|
this._element.classList.remove(ASYNC_TABLE_INITIALIZED_CLASS);
|
||||||
|
this._element.dataset['currentTableUrl'] = url.href;
|
||||||
// update table with new
|
// update table with new
|
||||||
this._element.innerHTML = response.element.innerHTML;
|
this._element.innerHTML = response.element.innerHTML;
|
||||||
|
|
||||||
this._app.utilRegistry.initAll(this._element);
|
this._app.utilRegistry.initAll(this._element);
|
||||||
|
|
||||||
if (callback && typeof callback === 'function') {
|
if (callback && typeof callback === 'function') {
|
||||||
this._storageManager.save('cssIdPrefix', response.idPrefix);
|
this._windowStorage.save('cssIdPrefix', response.idPrefix);
|
||||||
callback(this._element);
|
callback(this._element);
|
||||||
this._storageManager.remove('cssIdPrefix');
|
this._windowStorage.remove('cssIdPrefix');
|
||||||
}
|
}
|
||||||
}).catch((err) => console.error(err)
|
}).catch((err) => console.error(err)
|
||||||
).finally(() => this._element.classList.remove(ASYNC_TABLE_LOADING_CLASS));
|
).finally(() => this._element.classList.remove(ASYNC_TABLE_LOADING_CLASS));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_debugLog() {}
|
||||||
|
// _debugLog(fName, ...args) {
|
||||||
|
// console.log(`[DEBUGLOG] AsyncTable.${fName}`, { args: args, instance: this });
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -552,7 +552,7 @@ defaultDBSFilterLayout filterWdgt filterEnctype filterAction scrolltable
|
|||||||
{ formMethod = GET
|
{ formMethod = GET
|
||||||
, formAction = Just filterAction
|
, formAction = Just filterAction
|
||||||
, formEncoding = filterEnctype
|
, formEncoding = filterEnctype
|
||||||
, formAttrs = [("class", "table-filter-form")]
|
, formAttrs = [("class", "table-filter-form"), ("autocomplete", "off")]
|
||||||
, formSubmit = FormAutoSubmit
|
, formSubmit = FormAutoSubmit
|
||||||
, formAnchor = Nothing :: Maybe Text
|
, formAnchor = Nothing :: Maybe Text
|
||||||
}
|
}
|
||||||
@ -933,7 +933,7 @@ dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> db
|
|||||||
getParams <- liftHandler $ queryToQueryText . Wai.queryString . reqWaiRequest <$> getRequest
|
getParams <- liftHandler $ queryToQueryText . Wai.queryString . reqWaiRequest <$> getRequest
|
||||||
let
|
let
|
||||||
tblLink :: (QueryText -> QueryText) -> SomeRoute UniWorX
|
tblLink :: (QueryText -> QueryText) -> SomeRoute UniWorX
|
||||||
tblLink f = SomeRoute . (currentRoute, ) . over (mapped . _2) (fromMaybe Text.empty) $ (f . substPi . setParam "_hasdata" Nothing) getParams
|
tblLink f = SomeRoute . (currentRoute, ) . over (mapped . _2) (fromMaybe Text.empty) $ (f . substPi . setParam "_hasdata" Nothing . setParam (toPathPiece PostFormIdentifier) Nothing) getParams
|
||||||
substPi = foldr (.) id
|
substPi = foldr (.) id
|
||||||
[ setParams (wIdent "sorting") . map toPathPiece $ fromMaybe [] piSorting
|
[ setParams (wIdent "sorting") . map toPathPiece $ fromMaybe [] piSorting
|
||||||
, foldr (.) id . map (\k -> setParams (dbFilterKey dbtIdent' k) . fromMaybe [] . join $ traverse (Map.lookup k) piFilter) $ Map.keys dbtFilter
|
, foldr (.) id . map (\k -> setParams (dbFilterKey dbtIdent' k) . fromMaybe [] . join $ traverse (Map.lookup k) piFilter) $ Map.keys dbtFilter
|
||||||
@ -1306,7 +1306,7 @@ dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> db
|
|||||||
{ formMethod = GET
|
{ formMethod = GET
|
||||||
, formAction = Just . SomeRoute $ rawAction :#: wIdent "table-wrapper"
|
, formAction = Just . SomeRoute $ rawAction :#: wIdent "table-wrapper"
|
||||||
, formEncoding = pagesizeEnc
|
, formEncoding = pagesizeEnc
|
||||||
, formAttrs = [("class", "pagesize")]
|
, formAttrs = [("class", "pagesize"), ("autocomplete", "off")]
|
||||||
, formSubmit = FormAutoSubmit
|
, formSubmit = FormAutoSubmit
|
||||||
, formAnchor = Just $ wIdent "pagesize-form"
|
, formAnchor = Just $ wIdent "pagesize-form"
|
||||||
}
|
}
|
||||||
@ -1315,6 +1315,7 @@ dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> db
|
|||||||
|
|
||||||
csvWdgt = $(widgetFile "table/csv-transcode")
|
csvWdgt = $(widgetFile "table/csv-transcode")
|
||||||
|
|
||||||
|
uiLayout :: Widget -> Widget
|
||||||
uiLayout table = dbsFilterLayout filterWdgt filterEnc (SomeRoute $ rawAction :#: wIdent "table-wrapper") $(widgetFile "table/layout")
|
uiLayout table = dbsFilterLayout filterWdgt filterEnc (SomeRoute $ rawAction :#: wIdent "table-wrapper") $(widgetFile "table/layout")
|
||||||
|
|
||||||
dbInvalidateResult' = foldr (<=<) return . catMaybes $
|
dbInvalidateResult' = foldr (<=<) return . catMaybes $
|
||||||
@ -1344,7 +1345,14 @@ dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> db
|
|||||||
redirect $ tblLink id
|
redirect $ tblLink id
|
||||||
(act, _) -> act
|
(act, _) -> act
|
||||||
|
|
||||||
dbInvalidateResult' <=< bool (dbHandler (Proxy @m) (Proxy @x) $ (\table -> $(widgetFile "table/layout-wrapper")) . uiLayout) (sendResponse <=< tblLayout . uiLayout <=< dbWidget (Proxy @m) (Proxy @x)) psShortcircuit <=< runDBTable dbtable paginationInput currentKeys . fmap swap $ runWriterT table'
|
let
|
||||||
|
wrapLayout :: DBResult m x -> DB (DBResult m x)
|
||||||
|
wrapLayout = dbHandler (Proxy @m) (Proxy @x) $ (\table -> $(widgetFile "table/layout-wrapper")) . uiLayout
|
||||||
|
shortcircuit :: forall void. DBResult m x -> DB void
|
||||||
|
shortcircuit res = do
|
||||||
|
addCustomHeader HeaderDBTableCanonicalURL =<< toTextUrl (tblLink substPi)
|
||||||
|
sendResponse =<< tblLayout . uiLayout =<< dbWidget (Proxy @m) (Proxy @x) res
|
||||||
|
dbInvalidateResult' <=< bool wrapLayout shortcircuit psShortcircuit <=< runDBTable dbtable paginationInput currentKeys . fmap swap $ runWriterT table'
|
||||||
where
|
where
|
||||||
tblLayout :: forall m'. (MonadHandler m', HandlerSite m' ~ UniWorX) => Widget -> m' Html
|
tblLayout :: forall m'. (MonadHandler m', HandlerSite m' ~ UniWorX) => Widget -> m' Html
|
||||||
tblLayout tbl' = do
|
tblLayout tbl' = do
|
||||||
@ -1357,6 +1365,7 @@ dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> db
|
|||||||
setParam :: Text -> Maybe Text -> QueryText -> QueryText
|
setParam :: Text -> Maybe Text -> QueryText -> QueryText
|
||||||
setParam key = setParams key . maybeToList
|
setParam key = setParams key . maybeToList
|
||||||
|
|
||||||
|
|
||||||
dbTableWidget :: Monoid x
|
dbTableWidget :: Monoid x
|
||||||
=> PSValidator (HandlerFor UniWorX) x
|
=> PSValidator (HandlerFor UniWorX) x
|
||||||
-> DBTable (HandlerFor UniWorX) x
|
-> DBTable (HandlerFor UniWorX) x
|
||||||
|
|||||||
@ -841,7 +841,7 @@ choice = foldr (<|>) empty
|
|||||||
-- Custom HTTP Headers --
|
-- Custom HTTP Headers --
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|
||||||
data CustomHeader = HeaderIsModal | HeaderDBTableShortcircuit | HeaderMassInputShortcircuit | HeaderAlerts
|
data CustomHeader = HeaderIsModal | HeaderDBTableShortcircuit | HeaderMassInputShortcircuit | HeaderAlerts | HeaderDBTableCanonicalURL
|
||||||
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic)
|
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic)
|
||||||
|
|
||||||
instance Universe CustomHeader
|
instance Universe CustomHeader
|
||||||
|
|||||||
@ -3,10 +3,10 @@ $newline never
|
|||||||
$maybe flag <- sortableKey
|
$maybe flag <- sortableKey
|
||||||
$case directions
|
$case directions
|
||||||
$of [SortAsc]
|
$of [SortAsc]
|
||||||
<a .table__th-link rel=nofollow href=^{tblLink' $ setParams (wIdent "sorting") (map toPathPiece (SortingSetting flag SortDesc : piSorting'))}>
|
<a .table__th-link rel=nofollow href=^{tblLink' $ setParams (wIdent "sorting") (map toPathPiece (SortingSetting flag SortDesc : piSorting')) . substPi}>
|
||||||
^{widget}
|
^{widget}
|
||||||
$of _
|
$of _
|
||||||
<a .table__th-link rel=nofollow href=^{tblLink' $ setParams (wIdent "sorting") (map toPathPiece (SortingSetting flag SortAsc : piSorting'))}>
|
<a .table__th-link rel=nofollow href=^{tblLink' $ setParams (wIdent "sorting") (map toPathPiece (SortingSetting flag SortAsc : piSorting')) . substPi}>
|
||||||
^{widget}
|
^{widget}
|
||||||
$nothing
|
$nothing
|
||||||
^{widget}
|
^{widget}
|
||||||
|
|||||||
Reference in New Issue
Block a user