@eclipse-scout/core
Version:
Eclipse Scout runtime
373 lines (333 loc) • 11.6 kB
text/typescript
/*
* 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>;