UNPKG

@eclipse-scout/core

Version:
373 lines (333 loc) 11.6 kB
/* * Copyright (c) 2010, 2025 BSI Business Systems Integration AG * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 */ import {arrays, ChildModelOf, FullModelOf, InitModelOf, LookupCallModel, LookupResult, LookupRow, objects, ObjectType, ObjectWithType, QueryBy, scout, Session, SomeRequired} from '../index'; import $ from 'jquery'; /** * Base class for lookup calls. A concrete implementation of LookupCall which uses resources over a network * must deal with I/O errors and set, in case of an error, the 'exception' property on the returned lookup result. * The lookup call must _always_ return a result, otherwise the SmartField cannot work properly. */ export class LookupCall<TKey> implements LookupCallModel<TKey>, ObjectWithType { declare model: LookupCallModel<TKey>; declare initModel: SomeRequired<this['model'], 'session'>; objectType: string; session: Session; hierarchical: boolean; loadIncremental: boolean; batch: boolean; queryBy: QueryBy; searchText: string; key: TKey; keys: TKey[]; parentKey: TKey; active: boolean; maxRowCount: number; constructor() { this.session = null; this.hierarchical = false; this.loadIncremental = false; this.batch = false; this.queryBy = null; this.searchText = null; this.key = null; this.keys = null; this.parentKey = null; this.active = null; this.maxRowCount = 100; } init(model: InitModelOf<this>) { scout.assertParameter('session', model.session); this._init(model); } protected _init(model: InitModelOf<this>) { $.extend(this, model); } setLoadIncremental(loadIncremental: boolean) { this.loadIncremental = loadIncremental; } setHierarchical(hierarchical: boolean) { this.hierarchical = hierarchical; } setBatch(batch: boolean) { this.batch = batch; } /** * @param maxRowCount - a positive number, _not_ null or undefined! */ setMaxRowCount(maxRowCount: number) { this.maxRowCount = maxRowCount; } /** * This method may be called directly on any {@link LookupCall}. For the key lookup an internal clone is created automatically. * * You should not override this function. Instead override {@link _textByKey}. * * @returns a promise which returns a text of the lookup row resolved by {@link getByKey}. */ textByKey(key: TKey): JQuery.Promise<string> { if (objects.isNullOrUndefined(key)) { return $.resolvedPromise(''); } return this._textByKey(key); } /** * Override this function to provide your own textByKey implementation. * * @returns a promise which returns a text of the lookup row resolved by {@link getByKey}. */ protected _textByKey(key: TKey): JQuery.Promise<string> { return this .cloneForKey(key) .execute() .then(result => { let lookupRow = LookupCall.firstLookupRow(result); return lookupRow ? lookupRow.text : ''; }); } /** * This method may be called directly on any {@link LookupCall}. For the keys lookup an internal clone is created automatically. * * You should not override this function. Instead override {@link _textsByKeys}. * * @returns A promise which returns an object that maps every {@link LookupRow} key to the text of the resolved {@link LookupRow}. */ textsByKeys(keys: TKey[]): JQuery.Promise<Record<string, string>> { if (arrays.empty(keys)) { return $.resolvedPromise({}); } return this._textsByKeys(keys); } /** * Override this function to provide your own textsByKeys implementation. * * * @returns A promise which returns an object that maps every {@link LookupRow} key to the text of the resolved {@link LookupRow}. */ protected _textsByKeys(keys: TKey[]): JQuery.Promise<Record<string, string>> { return this .cloneForKeys(keys) .execute() .then(result => { if (!result || !objects.isArray(result.lookupRows)) { return {}; } const textMap = {}; result.lookupRows.forEach(row => { textMap[objects.ensureValidKey(row.key)] = row.text; }); return textMap; }); } /** * Only call this function if this LookupCall is not used again. Otherwise, use {@link cloneForAll().execute()} or {@link clone().getAll()}. * * You should not override this function. Instead override {@link _getAll}. */ getAll(): JQuery.Promise<LookupResult<TKey>> { this.queryBy = QueryBy.ALL; return this._getAll(); } /** * Override this method to implement. */ protected _getAll(): JQuery.Promise<LookupResult<TKey>> { throw new Error('_getAll() not implemented'); } /** * Only call this function if this {@link LookupCall} is not used again. Otherwise, use {@link cloneForText(text).execute()} or {@link clone().getByText(text)}. * * You should not override this function. Instead override {@link _getByText}. */ getByText(text: string): JQuery.Promise<LookupResult<TKey>> { this.queryBy = QueryBy.TEXT; this.searchText = text; return this._getByText(text); } /** * Override this method to implement. */ protected _getByText(text: string): JQuery.Promise<LookupResult<TKey>> { throw new Error('_getByText() not implemented'); } /** * Only call this function if this {@link LookupCall} is not used again. Otherwise, use {@link cloneForKey(key).execute()} or {@link clone().getByKey(parentKey)}. * * You should not override this function. Instead override {@link _getByKey}. */ getByKey(key: TKey): JQuery.Promise<LookupResult<TKey>> { this.queryBy = QueryBy.KEY; this.key = key; return this._getByKey(key); } /** * Override this method to implement. */ protected _getByKey(key: TKey): JQuery.Promise<LookupResult<TKey>> { throw new Error('getByKey() not implemented'); } /** * Only call this function if this {@link LookupCall} is not used again. Otherwise, use {@link cloneForKeys(keys).execute()} or {@link clone().getByKeys(keys)}. * * You should not override this function. Instead override {@link _getByKeys}. */ getByKeys(keys: TKey[]): JQuery.Promise<LookupResult<TKey>> { this.queryBy = QueryBy.KEYS; this.keys = keys; return this._getByKeys(keys); } /** * Override this method to implement. */ protected _getByKeys(keys: TKey[]): JQuery.Promise<LookupResult<TKey>> { throw new Error('_getByKeys() not implemented'); } /** * Only call this function if this {@link LookupCall} is not used again. Otherwise, use {@link cloneForRec(parentKey).execute()} or {@link clone().getByRec(parentKey)}. * * You should not override this function. Instead override {@link _getByRec}. * * Returns a result with lookup rows for the given parent key. This is used for incremental lookups. * * @param parentKey references the parent key */ getByRec(parentKey: TKey): JQuery.Promise<LookupResult<TKey>> { this.queryBy = QueryBy.REC; this.parentKey = parentKey; if (objects.isNullOrUndefined(parentKey)) { // Lookup rows with key = null cannot act as parent since lookup rows with parentKey = null are always top level return this._emptyRecResult(parentKey); } return this._getByRec(parentKey); } protected _emptyRecResult(rec: TKey): JQuery.Promise<LookupResult<TKey>> { return $.resolvedPromise({ queryBy: QueryBy.REC, rec: rec, lookupRows: [] }); } /** * Override this method to implement. */ protected _getByRec(rec: TKey): JQuery.Promise<LookupResult<TKey>> { throw new Error('_getByRec() not implemented'); } /** * Executes this LookupCall. For this method to work this LookupCall must be a clone created with one of the following methods: * {@link cloneForAll()}, {@link cloneForText(text)}, {@link cloneForKey(key)}, {@link cloneForRec(parentKey)} */ execute(): JQuery.Promise<LookupResult<TKey>> { if (QueryBy.KEY === this.queryBy) { return this._getByKey(this.key); } if (QueryBy.ALL === this.queryBy) { return this._getAll(); } if (QueryBy.KEYS === this.queryBy) { return this._getByKeys(this.keys); } if (QueryBy.TEXT === this.queryBy) { return this._getByText(this.searchText); } if (QueryBy.REC === this.queryBy) { if (objects.isNullOrUndefined(this.parentKey)) { // Lookup rows with key = null cannot act as parent since lookup rows with parentKey = null are always top level return this._emptyRecResult(this.parentKey); } return this._getByRec(this.parentKey); } throw new Error('cannot execute a non-clone LookupCall. Use one of the cloneFor*-methods before executing.'); } /** * @param properties Properties to add to the resulting clone instance. */ clone(properties?: object): this { // Warning: This is _not_ a deep clone! (Because otherwise the entire session would be duplicated.) // Non-primitive properties must _only_ be added to the resulting clone during the 'prepareLookupCall' event! return scout.cloneShallow(this, properties, true) as this; } cloneForAll(): this { return this.clone({ queryBy: QueryBy.ALL }); } cloneForText(text: string): this { return this.clone({ queryBy: QueryBy.TEXT, searchText: text }); } cloneForKey(key: TKey): this { return this.clone({ queryBy: QueryBy.KEY, key: key }); } cloneForKeys(keys: TKey[]): this { return this.clone({ queryBy: QueryBy.KEYS, keys: keys }); } cloneForRec(parentKey: TKey): this { return this.clone({ queryBy: QueryBy.REC, parentKey: parentKey }); } abort() { // NOP. Implement in subclasses if necessary. } /** * Creates a lookup result with the given lookup rows. If the lookup call fails, an error message (exception) should be passed. * * Can be used by concrete lookup calls if needed. */ protected _createLookupResult(lookupRows: LookupRow<TKey>[], exception?: string): LookupResult<TKey> { return { queryBy: this.queryBy, text: this.searchText, key: this.key, lookupRows: lookupRows, exception: exception }; } // ---- static helpers ---- static ensure<TKey, TLookupCall extends LookupCall<TKey>>(lookupCall: LookupCallOrModel<TKey, TLookupCall>, session: Session): TLookupCall { if (!lookupCall) { return lookupCall as TLookupCall; } if (lookupCall instanceof LookupCall) { return lookupCall; } if (typeof lookupCall === 'string' || typeof lookupCall === 'function') { return scout.create(lookupCall, { session: session } as InitModelOf<TLookupCall>); } lookupCall.session = session; return scout.create(lookupCall as FullModelOf<TLookupCall>); } static firstLookupRow<K>(result: LookupResult<K>): LookupRow<K> { if (!result) { return null; } if (!objects.isArray(result.lookupRows)) { return null; } if (result.lookupRows.length === 0) { return null; } return result.lookupRows[0]; } } /** * A LookupCall, a LookupCallModel or a string with a LookupCall class name. */ export type LookupCallOrModel<TKey, TLookupCall extends LookupCall<TKey> = LookupCall<TKey>> = TLookupCall | ChildModelOf<TLookupCall> | ObjectType<TLookupCall>;