@knowmax/genericlist-core
Version:
Knowmax Generic list with basic CRUD support without any user interface implementation.
395 lines (394 loc) • 14.7 kB
JavaScript
import { action, computed, makeObservable, observable } from "mobx";
import { GenericListSettings } from "./GenericListSettings";
/** Generic implementation to support lists from different endpoints supporting Knowmax LinqUtility ListRequest.
* Can be inherited or used in combination with GenericListHook, @see useList. */
export class GenericList {
constructor(configuration) {
Object.defineProperty(this, "configuration", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "_settingsUpdated", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "_loadOptions", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/** Unique id representing this list. Will be provided to methods from settings when called to determine active list. Optional due to backward compatibility. */
Object.defineProperty(this, "id", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/** Settings for all generic lists. */
Object.defineProperty(this, "settings", {
enumerable: true,
configurable: true,
writable: true,
value: new GenericListSettings()
});
/** Set while loading a single item */
Object.defineProperty(this, "loadingSingle", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
/** Set after initially loading data. */
Object.defineProperty(this, "loaded", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
/** Set while loading data. */
Object.defineProperty(this, "loading", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "error", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/** Available options for order. */
Object.defineProperty(this, "orderList", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/** Selected order expression. Set to IOrder from orderList or string order expression. */
Object.defineProperty(this, "order", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "deleted", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
/** List with all filter definitions available for this list. */
Object.defineProperty(this, "filterList", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/** Filter to use while loading data. */
Object.defineProperty(this, "filter", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/** Query to be used while loading data. */
Object.defineProperty(this, "search", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/** Current selected page. 1-based. */
Object.defineProperty(this, "page", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/** Number of items per page. */
Object.defineProperty(this, "pageSize", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/** List containing actual page of data. */
Object.defineProperty(this, "list", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/** Total number of items available. */
Object.defineProperty(this, "totalCount", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/** Will be used as post fix for currently configured endpoint. */
Object.defineProperty(this, "endpointPostFix", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/** Hash at the moment of last update of list with server data. */
Object.defineProperty(this, "currentHash", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/** Set to selected item for specific purpose. */
Object.defineProperty(this, "selectedItem", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/** Tag to identify purpose of selected item.. */
Object.defineProperty(this, "selectedItemTag", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
makeObservable(this, {
ready: computed,
id: observable,
setId: action,
settings: observable,
setSettings: action,
loadingSingle: observable,
setLoadingSingle: action,
loaded: observable,
setLoaded: action,
loading: observable,
setLoading: action,
error: observable,
setError: action,
orderList: observable,
setOrderList: action,
order: observable,
setOrder: action,
orderExpression: computed,
deleted: observable,
setDeleted: action,
filterList: observable,
setFilterList: action,
filter: observable,
setFilter: action,
search: observable,
setSearch: action,
page: observable,
setPage: action,
pageSize: observable,
setPageSize: action,
list: observable,
setList: action,
totalCount: observable,
setTotalCount: action,
endpointPostFix: observable,
setEndpointPostFix: action,
hash: computed,
currentHash: observable,
setCurrentHash: action,
selectedItem: observable,
selectedItemTag: observable,
setSelectedItem: action,
load: action,
loadSingle: action
});
this.configuration = configuration;
this.id = configuration.id;
this.orderList = configuration.orderList ?? [];
this.order = configuration.orderList?.find(o => o.default === true);
this.filterList = configuration.filterList ?? [];
this.list = [];
this.page = configuration.page ?? 1;
this.pageSize = configuration.pageSize ?? 10;
this.totalCount = 0;
}
/** Set when this generic list is ready for use. Essentially this means settings configuration is ready. */
get ready() {
return this.settings.ready;
}
setId(value) {
this.id = value;
}
/** Usually set from value provided GenericListSettinsProvider. */
setSettings(value) {
this._settingsUpdated = true;
this.settings = value;
}
setLoadingSingle(value) {
this.loadingSingle = value;
}
setLoaded(value) {
this.loaded = value;
}
setLoading(value) {
this.loading = value;
}
setError(value) {
this.error = value;
}
setOrderList(value) {
this.orderList = value;
}
setOrder(value) {
this.order = value;
}
/** Server expression based on current order setting. */
get orderExpression() {
return this.order ? typeof this.order !== 'string' ? `${this.order.field}${this.order.descending === true ? ' desc' : ''}` : this.order : '';
}
setDeleted(value) {
this.deleted = value;
}
setFilterList(value) {
this.filterList = value;
}
setFilter(value) {
this.filter = value;
}
setSearch(value) {
this.search = value;
}
setPage(value) {
this.page = value;
}
setPageSize(value) {
this.pageSize = value;
}
setList(value) {
this.list = value;
}
setTotalCount(value) {
this.totalCount = value;
}
/** Set to postfix for current endpoint. For example the value "abc" will result in "/abc" postfix after currently configured endpoint. */
setEndpointPostFix(value) {
this.endpointPostFix = value;
}
/** Dynamic hash representing current state of component. Use as dependecy to trigger effects */
get hash() {
return `id=${this.id}&p=${this.page}&ps=${this.pageSize}&o=${this.orderExpression}&f=${this.filter}&s=${this.search}${this.deleted ? '&d=true' : ''}&t=${this.settings.token}&epf=${this.endpointPostFix}`;
}
setCurrentHash(value) {
this.currentHash = value;
}
/** Set when hash was updated. Rather use hash as dependecy to trigger effects. */
get hashUpdated() {
return this.hash !== this.currentHash;
}
/** Selects item for specific purpose (not edit or delete). */
setSelectedItem(value, tag) {
this.selectedItem = value;
this.selectedItemTag = value ? tag : undefined;
}
/** Load current page of items.
* @param reload If true, will reload current page of items using original options used on inital load.
* @param options Options to use while loading data. If not provided, will use options used on inital load only in case of reload.
*/
async load(reload = false, options) {
if (!this.settings.token || (this.configuration.requiredEndpointPostfix === true && (!this.endpointPostFix || this.endpointPostFix.trim() === ''))) {
this.setList([]);
this.setTotalCount(0);
this.setLoaded(false);
}
else if (this.hashUpdated || reload) {
try {
try {
this.checkSettings();
this.setCurrentHash(this.hash);
this.setError(undefined);
this.setLoading(true);
// On reload use options from initial load in case no new options are provided.
if (reload && !options && this._loadOptions) {
options = this._loadOptions;
}
else if (!reload) {
this._loadOptions = options;
}
const endpoint = (this.configuration.requiredEndpointPostfix === true && this.endpointPostFix && this.endpointPostFix.trim() !== '') ?
`${this.configuration.endpoint}/${this.endpointPostFix}` :
this.configuration.endpoint;
const method = options?.method ?? 'post';
const searchparams = { ...options?.searchParams };
// Dynamically add deleted parameter if this.deleted is true
if (this.deleted) {
searchparams.deleted = 'true';
}
const response = await this.settings.onFetch(endpoint, {
searchParams: searchparams, //options?.searchParams,
method: method,
headers: options?.headers ?? this.settings.onGetHeaders(this.settings.token, this.settings.language, undefined, this.id),
// Make sure we never post json when not post was specified as method
json: method === 'post' ? options?.json ?? {
count: true,
take: this.pageSize,
skip: (this.page - 1) * this.pageSize,
orderBy: this.orderExpression,
filter: this.filter,
search: this.search
} : undefined
}, this.id);
this.setList(response.value ?? []);
this.setTotalCount(response.count ?? 0);
this.setLoaded(true);
}
catch (e) {
if (e instanceof Error) {
this.setError(e);
}
this.setLoaded(false);
}
}
finally {
this.setLoading(false);
}
}
}
/** Load single item identified by id.
* @param id Id of item to load.
* @param options Options to use while loading data.
* @param endpoint Endpoint to use while loading data. If not provided, will use endpointGetSingle or endpoint from configuration.
*/
async loadSingle(id, options, endpoint) {
this.setError(undefined);
if (this.settings.token) {
try {
this.checkSettings();
this.setLoadingSingle(true);
try {
return await this.settings.onFetch(`${endpoint ?? this.configuration.endpointGetSingle ?? this.configuration.endpoint}/${id}`, {
searchParams: options?.searchParams,
method: options?.method ?? 'get',
headers: options?.headers ?? this.settings.onGetHeaders(this.settings.token, this.settings.language, undefined, this.id)
}, this.id);
}
catch (e) {
if (e instanceof Error) {
this.setError(e);
}
}
}
finally {
this.setLoadingSingle(false);
}
}
}
checkSettings() {
if (!this._settingsUpdated) {
console.warn(`Settings not set for generic list with endpoint ${this.configuration.endpoint}`);
}
}
}