parent
9e47a7cd28
commit
c348b7cb03
@ -9,9 +9,10 @@ export const LOCATION = {
|
||||
LOCAL: 'local',
|
||||
SESSION: 'session',
|
||||
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 {
|
||||
|
||||
@ -26,7 +27,16 @@ export class StorageManager {
|
||||
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);
|
||||
|
||||
if (!namespace) {
|
||||
@ -48,7 +58,7 @@ export class StorageManager {
|
||||
throw new Error('Cannot setup StorageManager without window or global');
|
||||
|
||||
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];
|
||||
if (encryption) this._requestStorageKey({ location: location, encryption: encryption });
|
||||
});
|
||||
@ -70,17 +80,21 @@ export class StorageManager {
|
||||
|
||||
switch (location) {
|
||||
case LOCATION.LOCAL: {
|
||||
this._saveToLocalStorage(this._updateStorage(this._getFromLocalStorage(options), { [key]: value }, LOCATION.LOCAL, options));
|
||||
this._saveToLocalStorage({ ...this._getFromLocalStorage(options), [key]: value}, options);
|
||||
break;
|
||||
}
|
||||
case LOCATION.SESSION: {
|
||||
this._saveToSessionStorage(this._updateStorage(this._getFromSessionStorage(options), { [key]: value }, LOCATION.SESSION, options));
|
||||
this._saveToSessionStorage({ ...this._getFromSessionStorage(options), [key]: value}, options);
|
||||
break;
|
||||
}
|
||||
case LOCATION.WINDOW: {
|
||||
this._saveToWindow({ ...this._getFromWindow(), [key]: value });
|
||||
break;
|
||||
}
|
||||
case LOCATION.HISTORY: {
|
||||
this._saveToHistory({ ...this._getFromHistory(), [key]: value }, options);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console.error('StorageManager.save cannot save item with unsupported location');
|
||||
}
|
||||
@ -112,6 +126,10 @@ export class StorageManager {
|
||||
val = this._getFromWindow()[key];
|
||||
break;
|
||||
}
|
||||
case LOCATION.HISTORY: {
|
||||
val = this._getFromHistory(options)[key];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console.error('StorageManager.load cannot load item with unsupported location');
|
||||
}
|
||||
@ -138,14 +156,14 @@ export class StorageManager {
|
||||
|
||||
delete val[key];
|
||||
|
||||
return this._saveToLocalStorage(val);
|
||||
return this._saveToLocalStorage(val, options);
|
||||
}
|
||||
case LOCATION.SESSION: {
|
||||
let val = this._getFromSessionStorage(options);
|
||||
|
||||
delete val[key];
|
||||
|
||||
return this._saveToSessionStorage(val);
|
||||
return this._saveToSessionStorage(val, options);
|
||||
}
|
||||
case LOCATION.WINDOW: {
|
||||
let val = this._getFromWindow();
|
||||
@ -154,6 +172,14 @@ export class StorageManager {
|
||||
|
||||
return this._saveToWindow(val);
|
||||
}
|
||||
case LOCATION.HISTORY: {
|
||||
let val = this._getFromHistory(options);
|
||||
|
||||
delete val[key];
|
||||
|
||||
return this._saveToHistory(val, options);
|
||||
}
|
||||
|
||||
default:
|
||||
console.error('StorageManager.load cannot load item with unsupported location');
|
||||
}
|
||||
@ -177,6 +203,8 @@ export class StorageManager {
|
||||
return this._clearSessionStorage();
|
||||
case LOCATION.WINDOW:
|
||||
return this._clearWindow();
|
||||
case LOCATION.HISTORY:
|
||||
return this._clearHistory(options && options.history);
|
||||
default:
|
||||
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);
|
||||
|
||||
let state;
|
||||
@ -210,7 +238,7 @@ export class StorageManager {
|
||||
}
|
||||
}
|
||||
|
||||
_saveToLocalStorage(state) {
|
||||
_saveToLocalStorage(state, options) {
|
||||
this._debugLog('_saveToLocalStorage', state);
|
||||
|
||||
if (!state)
|
||||
@ -223,8 +251,8 @@ export class StorageManager {
|
||||
} else {
|
||||
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() {
|
||||
@ -240,10 +268,10 @@ export class StorageManager {
|
||||
if (!this._global || !this._global.App)
|
||||
return {};
|
||||
|
||||
if (!this._global.App.Storage)
|
||||
this._global.App.Storage = {};
|
||||
if (!this._global.App.Storage || !this._global.App.Storage[this.namespace])
|
||||
return {};
|
||||
|
||||
return this._global.App.Storage;
|
||||
return this._global.App.Storage[this.namespace];
|
||||
}
|
||||
|
||||
_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);
|
||||
|
||||
let state;
|
||||
@ -300,7 +391,7 @@ export class StorageManager {
|
||||
}
|
||||
}
|
||||
|
||||
_saveToSessionStorage(state) {
|
||||
_saveToSessionStorage(state, options) {
|
||||
this._debugLog('_saveToSessionStorage', state);
|
||||
|
||||
if (!state)
|
||||
@ -314,7 +405,7 @@ export class StorageManager {
|
||||
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() {
|
||||
@ -324,10 +415,10 @@ export class StorageManager {
|
||||
}
|
||||
|
||||
|
||||
_getFromStorage(storage, location, options=this._options) {
|
||||
_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) {
|
||||
return { ...storage, ...JSON.parse(decrypt(storage.encryption.ciphertext, this._encryptionKey[location]) || null) };
|
||||
} 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);
|
||||
|
||||
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) {
|
||||
const updatedDecryptedStorage = { ...JSON.parse(decrypt(storage.encryption.ciphertext, this._encryptionKey[location]) || null), ...update };
|
||||
console.log('updatedDecryptedStorage', updatedDecryptedStorage);
|
||||
@ -357,13 +448,13 @@ export class StorageManager {
|
||||
const enc = this.load('encryption', { ...options, encryption: false });
|
||||
const requestBody = {
|
||||
type : options.encryption,
|
||||
length : 42,
|
||||
length : sodium.crypto_secretbox_KEYBYTES,
|
||||
salt : enc.salt,
|
||||
timestamp : enc.timestamp,
|
||||
};
|
||||
|
||||
this._global.App.httpClient.post({
|
||||
url: '../../../../../../user/storage-key', // TODO use APPROOT instead
|
||||
url: '/user/storage-key',
|
||||
headers: {
|
||||
'Content-Type' : HttpClient.ACCEPT.JSON,
|
||||
'Accept' : HttpClient.ACCEPT.JSON,
|
||||
@ -381,11 +472,10 @@ export class StorageManager {
|
||||
}).catch(console.error);
|
||||
}
|
||||
|
||||
_debugLog() {
|
||||
_debugLog() {}
|
||||
// _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();
|
||||
this._prefixIds(element, idPrefix);
|
||||
}
|
||||
return Promise.resolve({ idPrefix, element });
|
||||
return Promise.resolve({ idPrefix, element, headers: response.headers });
|
||||
},
|
||||
Promise.reject,
|
||||
).catch(console.error);
|
||||
|
||||
@ -13,7 +13,9 @@ const INPUT_DEBOUNCE = 600;
|
||||
const FILTER_DEBOUNCE = 100;
|
||||
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_INITIALIZED_CLASS = 'async-table--initialized';
|
||||
const ASYNC_TABLE_LOADING_CLASS = 'async-table--loading';
|
||||
@ -31,6 +33,8 @@ export class AsyncTable {
|
||||
_asyncTableHeader;
|
||||
_asyncTableId;
|
||||
|
||||
_asyncTableIdent;
|
||||
|
||||
_ths = [];
|
||||
_pageLinks = [];
|
||||
_pagesizeForm;
|
||||
@ -47,7 +51,10 @@ export class AsyncTable {
|
||||
};
|
||||
_ignoreRequest = false;
|
||||
|
||||
_storageManager = new StorageManager(ASYNC_TABLE_LOCAL_STORAGE_KEY, '1.0.0', { location: LOCATION.WINDOW });
|
||||
_windowStorage;
|
||||
_historyStorage;
|
||||
|
||||
_active = true;
|
||||
|
||||
constructor(element, app) {
|
||||
if (!element) {
|
||||
@ -79,21 +86,58 @@ export class AsyncTable {
|
||||
this._cssIdPrefix = findCssIdPrefix(rawTableId);
|
||||
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
|
||||
this._scrollTable = this._element.querySelector(ASYNC_TABLE_SCROLLTABLE_SELECTOR);
|
||||
if (!this._scrollTable) {
|
||||
throw new Error('Async Table cannot be set up without a scrolltable element!');
|
||||
}
|
||||
|
||||
this._setupTableFilter();
|
||||
|
||||
this._processStorage();
|
||||
|
||||
// clear currentTableUrl from previous requests
|
||||
this._storageManager.remove('currentTableUrl');
|
||||
this._setupTableFilter();
|
||||
|
||||
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
|
||||
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() {
|
||||
@ -113,7 +157,7 @@ export class AsyncTable {
|
||||
|
||||
this._ths.forEach((th) => {
|
||||
th.clickHandler = (event) => {
|
||||
this._storageManager.save('horizPos', (this._scrollTable || {}).scrollLeft);
|
||||
this._windowStorage.save('horizPos', (this._scrollTable || {}).scrollLeft);
|
||||
this._linkClickHandler(event);
|
||||
};
|
||||
th.element.addEventListener('click', th.clickHandler);
|
||||
@ -135,7 +179,7 @@ export class AsyncTable {
|
||||
left: this._scrollTable.offsetLeft || 0,
|
||||
behavior: 'smooth',
|
||||
};
|
||||
this._storageManager.save('scrollTo', scrollTo);
|
||||
this._windowStorage.save('scrollTo', scrollTo);
|
||||
}
|
||||
this._linkClickHandler(event);
|
||||
};
|
||||
@ -256,7 +300,7 @@ export class AsyncTable {
|
||||
const prefix = findCssIdPrefix(focusedInput.id);
|
||||
const focusId = focusedInput.id.replace(prefix, '');
|
||||
callback = function(wrapper) {
|
||||
const idPrefix = this._storageManager.load('cssIdPrefix');
|
||||
const idPrefix = this._windowStorage.load('cssIdPrefix');
|
||||
const toBeFocused = wrapper.querySelector('#' + idPrefix + focusId);
|
||||
if (toBeFocused) {
|
||||
toBeFocused.focus();
|
||||
@ -268,34 +312,33 @@ export class AsyncTable {
|
||||
}
|
||||
|
||||
_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
|
||||
const formData = Datepicker.unformatAll(tableFilterForm, new FormData(tableFilterForm));
|
||||
|
||||
for (var k of url.searchParams.keys()) {
|
||||
url.searchParams.delete(k);
|
||||
}
|
||||
this._debugLog('_serializeTableFilterToURL', Array.from(formData.entries()), url.href);
|
||||
|
||||
for (var kv of formData.entries()) {
|
||||
url.searchParams.append(kv[0], kv[1]);
|
||||
}
|
||||
const searchParams = new URLSearchParams(Array.from(formData.entries()));
|
||||
url.search = searchParams.toString();
|
||||
|
||||
this._debugLog('_serializeTableFilterToURL', url.href);
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
_processStorage() {
|
||||
const scrollTo = this._storageManager.load('scrollTo');
|
||||
const scrollTo = this._windowStorage.load('scrollTo');
|
||||
if (scrollTo && this._scrollTable) {
|
||||
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) {
|
||||
this._scrollTable.scrollLeft = horizPos;
|
||||
}
|
||||
this._storageManager.remove('horizPos');
|
||||
this._windowStorage.remove('horizPos');
|
||||
}
|
||||
|
||||
_removeListeners() {
|
||||
@ -330,7 +373,7 @@ export class AsyncTable {
|
||||
}
|
||||
|
||||
_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
|
||||
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
|
||||
_updateTableFrom(url, callback) {
|
||||
_updateTableFrom(url, callback, isPopState) {
|
||||
url = new URL(url);
|
||||
|
||||
const cancelPendingUpdates = (() => {
|
||||
this._cancelPendingUpdates.forEach(f => f());
|
||||
}).bind(this);
|
||||
@ -372,23 +417,33 @@ export class AsyncTable {
|
||||
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
|
||||
this._removeListeners();
|
||||
this._active = false;
|
||||
this._element.classList.remove(ASYNC_TABLE_INITIALIZED_CLASS);
|
||||
this._element.dataset['currentTableUrl'] = url.href;
|
||||
// update table with new
|
||||
this._element.innerHTML = response.element.innerHTML;
|
||||
|
||||
this._app.utilRegistry.initAll(this._element);
|
||||
|
||||
if (callback && typeof callback === 'function') {
|
||||
this._storageManager.save('cssIdPrefix', response.idPrefix);
|
||||
this._windowStorage.save('cssIdPrefix', response.idPrefix);
|
||||
callback(this._element);
|
||||
this._storageManager.remove('cssIdPrefix');
|
||||
this._windowStorage.remove('cssIdPrefix');
|
||||
}
|
||||
}).catch((err) => console.error(err)
|
||||
).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
|
||||
, formAction = Just filterAction
|
||||
, formEncoding = filterEnctype
|
||||
, formAttrs = [("class", "table-filter-form")]
|
||||
, formAttrs = [("class", "table-filter-form"), ("autocomplete", "off")]
|
||||
, formSubmit = FormAutoSubmit
|
||||
, formAnchor = Nothing :: Maybe Text
|
||||
}
|
||||
@ -933,7 +933,7 @@ dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> db
|
||||
getParams <- liftHandler $ queryToQueryText . Wai.queryString . reqWaiRequest <$> getRequest
|
||||
let
|
||||
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
|
||||
[ 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
|
||||
@ -1306,7 +1306,7 @@ dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> db
|
||||
{ formMethod = GET
|
||||
, formAction = Just . SomeRoute $ rawAction :#: wIdent "table-wrapper"
|
||||
, formEncoding = pagesizeEnc
|
||||
, formAttrs = [("class", "pagesize")]
|
||||
, formAttrs = [("class", "pagesize"), ("autocomplete", "off")]
|
||||
, formSubmit = FormAutoSubmit
|
||||
, formAnchor = Just $ wIdent "pagesize-form"
|
||||
}
|
||||
@ -1315,6 +1315,7 @@ dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> db
|
||||
|
||||
csvWdgt = $(widgetFile "table/csv-transcode")
|
||||
|
||||
uiLayout :: Widget -> Widget
|
||||
uiLayout table = dbsFilterLayout filterWdgt filterEnc (SomeRoute $ rawAction :#: wIdent "table-wrapper") $(widgetFile "table/layout")
|
||||
|
||||
dbInvalidateResult' = foldr (<=<) return . catMaybes $
|
||||
@ -1344,7 +1345,14 @@ dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> db
|
||||
redirect $ tblLink id
|
||||
(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
|
||||
tblLayout :: forall m'. (MonadHandler m', HandlerSite m' ~ UniWorX) => Widget -> m' Html
|
||||
tblLayout tbl' = do
|
||||
@ -1356,6 +1364,7 @@ dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> db
|
||||
|
||||
setParam :: Text -> Maybe Text -> QueryText -> QueryText
|
||||
setParam key = setParams key . maybeToList
|
||||
|
||||
|
||||
dbTableWidget :: Monoid x
|
||||
=> PSValidator (HandlerFor UniWorX) x
|
||||
|
||||
@ -841,7 +841,7 @@ choice = foldr (<|>) empty
|
||||
-- Custom HTTP Headers --
|
||||
---------------------------------
|
||||
|
||||
data CustomHeader = HeaderIsModal | HeaderDBTableShortcircuit | HeaderMassInputShortcircuit | HeaderAlerts
|
||||
data CustomHeader = HeaderIsModal | HeaderDBTableShortcircuit | HeaderMassInputShortcircuit | HeaderAlerts | HeaderDBTableCanonicalURL
|
||||
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic)
|
||||
|
||||
instance Universe CustomHeader
|
||||
|
||||
@ -3,10 +3,10 @@ $newline never
|
||||
$maybe flag <- sortableKey
|
||||
$case directions
|
||||
$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}
|
||||
$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}
|
||||
$nothing
|
||||
^{widget}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user