UNPKG

@knowmax/genericlist-core

Version:

Knowmax Generic list with basic CRUD support without any user interface implementation.

395 lines (394 loc) 14.7 kB
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}`); } } }