@eclipse-scout/core
Version:
Eclipse Scout runtime
273 lines (235 loc) • 9.1 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 {AjaxCall, arrays, DoEntity, InitModelOf, LookupCall, LookupResult, LookupRow, objects, RestLookupCallModel, scout} from '../index';
import $ from 'jquery';
import Deferred = JQuery.Deferred;
/**
* A lookup call that can load lookup rows from a REST service.
*
* API:
* ----
* By default, the REST service is expected to listen for POST requests at the URL defined by
* this.resourceUrl. It receives a restriction object and must return a list of matching lookup rows.
* The serialization format is JSON.
*
* Lookup rows:
* ------------
* The standard lookup row properties defined by Scout are usually sufficient (see AbstractLookupRowDo.java).
*
* Restriction:
* ------------
* The restriction object consists of a number of 'well-known' properties (e.g. 'text' in {@link QueryBy.TEXT}
* mode, see AbstractLookupRestrictionDo.java for details) and additional, service-dependent properties
* that can either be predefined in the model or added programmatically at runtime. Since all of those
* properties are sent in the same restriction object, some care must be taken to prevent accidental
* overwriting of properties.
*
* Order of precedence (lowest to highest):
* 1. Restrictions automatically applied to all clones after their creation in the respective cloneFor method.
* These are: 'active' (ALL, TEXT, REC) and 'maxRowCount' (ALL, TEXT, REC)
* 2. Restrictions predefined in the model property 'restriction', shared by all clones.
* 3. Restrictions applied to clones programmatically, e.g. during a 'prepareLookupCall' event.
* 4. Hard-coded properties that are fundamental to the respective queryBy mode (cannot be overridden).
* These are: 'ids' (KEY, KEYS) and 'text' (TEXT)
*/
export class RestLookupCall<TKey> extends LookupCall<TKey> implements RestLookupCallModel<TKey> {
declare model: RestLookupCallModel<TKey>;
resourceUrl: string;
maxTextLength: number;
restriction: Record<string, any>;
protected _restriction: Record<string, any>;
protected _ajaxCall: AjaxCall;
protected _deferred: Deferred<LookupResult<TKey>, { abort: boolean }>;
constructor() {
super();
this.resourceUrl = null;
this.maxTextLength = null;
// for predefined restrictions only (e.g. in JSON or subclasses), don't change this attribute! this instance is shared with all clones!
this.restriction = null;
// dynamically added restrictions. after setting this attribute, this instance is shared with all following clones!
this._restriction = null;
this._ajaxCall = null;
this._deferred = null;
// RestLookupCall implements getByKeys
this.batch = true;
}
/**
* Use this function with caution! Added restrictions will be shared among cloned instances
* and the current instance if this function was also called before cloning!
*/
addRestriction(key: string, value: any) {
if (!this._restriction) {
this._restriction = {};
}
this._restriction[key] = value;
}
/**
* Adds the given key-value pair to 'this._restriction', but only if there is no predefined
* value for this key in 'this.restriction'. This prevents unintentional overriding of
* user-defined model restrictions.
*/
protected _addRestrictionIfAbsent(key: string, value: any) {
if (!this.restriction || objects.isNullOrUndefined(this.restriction[key])) {
this.addRestriction(key, value);
}
}
protected override _getAll(): JQuery.Promise<LookupResult<TKey>> {
return this._call();
}
protected override _getByText(text: string): JQuery.Promise<LookupResult<TKey>> {
this.addRestriction('text', text);
return this._call();
}
protected override _getByKey(key: TKey): JQuery.Promise<LookupResult<TKey>> {
this.addRestriction('ids', arrays.ensure(key));
return this._call();
}
protected override _getByKeys(keys: TKey[]): JQuery.Promise<LookupResult<TKey>> {
this.addRestriction('ids', arrays.ensure(keys));
return this._call();
}
override cloneForAll(): this {
let clone = super.cloneForAll();
clone._addRestrictionIfAbsent('active', true);
clone._addRestrictionIfAbsent('maxRowCount', this.maxRowCount);
return clone;
}
override cloneForText(text: string): this {
let clone = super.cloneForText(text);
clone._addRestrictionIfAbsent('active', true);
clone._addRestrictionIfAbsent('maxRowCount', this.maxRowCount);
return clone;
}
override cloneForRec(parentKey: TKey): this {
let clone = super.cloneForRec(parentKey);
clone._addRestrictionIfAbsent('active', true);
clone._addRestrictionIfAbsent('maxRowCount', this.maxRowCount);
return clone;
}
protected _acceptLookupRow(lookupRowDo: LookupRowDo<TKey>): boolean {
return true;
}
protected _createLookupRowFromDo(lookupRowDo: LookupRowDo<TKey>): LookupRow<TKey> {
// propagate all properties from lookup row DO to scout lookup row (there might be custom ones on specific lookup row DOs)
let clonedLookupRowDo = $.extend({}, lookupRowDo) as LookupRowDo<TKey> & InitModelOf<LookupRow<TKey>>;
// [text, enabled, active, iconId, cssClass, tooltipText, additionalTableRowData] are the same for LookupRow.ts and LookupRowDo.java
// [backgroundColor, foregroundColor, font] currently not supported by LookupRowDo.java
// id -> key
clonedLookupRowDo.key = clonedLookupRowDo.id;
delete clonedLookupRowDo.id;
// parentId -> parentKey
clonedLookupRowDo.parentKey = clonedLookupRowDo.parentId;
delete clonedLookupRowDo.parentId;
// unused on Scout LookupRow
delete clonedLookupRowDo._type;
if (this.maxTextLength) {
let text = clonedLookupRowDo.text;
if (text.length > this.maxTextLength) {
clonedLookupRowDo.text = text.substr(0, this.maxTextLength) + '...';
clonedLookupRowDo.tooltipText = text;
}
}
return scout.create((LookupRow<TKey>), clonedLookupRowDo);
}
protected _call(): JQuery.Promise<LookupResult<TKey>> {
this._deferred = $.Deferred();
this._ajaxCall = this._createAjaxCall();
this._ajaxCall.call()
.then((data: LookupResponse, textStatus, jqXHR) => {
let lookupRows = arrays.ensure(data ? data.rows : null)
.filter(this._acceptLookupRow.bind(this))
.map(this._createLookupRowFromDo.bind(this));
this._deferred.resolve(this._createLookupResult(lookupRows));
})
.catch(ajaxError => {
this._deferred.resolve(this._createLookupResult([], this.session.text('ErrorWhileLoadingData')));
});
return this._deferred.promise();
}
override abort() {
this._deferred?.reject({
abort: true
});
this._ajaxCall?.abort();
super.abort();
}
protected _getCallUrl(): string {
return this.resourceUrl;
}
protected _getRestrictionForAjaxCall(): Record<string, any> {
if (!this.restriction && !this._restriction) {
return null;
}
let resolveValue = value => {
if (typeof value === 'function') {
// Dynamic evaluation of the restriction value
return value(this);
}
return value;
};
let resolvedRestriction = {};
let restriction = $.extend({}, this.restriction, this._restriction);
Object.keys(restriction).forEach(key => {
let value = restriction[key];
let newValue;
if (Array.isArray(value)) {
// Resolve each array element individually, remove null values
newValue = arrays.flatMap(value, resolveValue).filter(Boolean);
newValue = newValue.length ? newValue : null;
} else {
newValue = resolveValue(value);
}
// Only add non-null restrictions
if (!objects.isNullOrUndefined(newValue)) {
resolvedRestriction[key] = newValue;
}
});
return resolvedRestriction;
}
protected _createAjaxCall(): AjaxCall {
let url = this._getCallUrl();
let restriction = this._getRestrictionForAjaxCall();
let data = restriction ? JSON.stringify(restriction) : null;
let ajaxOptions = {
method: 'POST',
data: data,
dataType: 'json',
contentType: 'application/json; charset=UTF-8',
cache: false,
url: url,
timeout: 0
};
return scout.create(AjaxCall, {
ajaxOptions: ajaxOptions,
name: 'RestLookupCall',
retryIntervals: [100, 500, 500, 500]
});
}
}
/**
* @see "AbstractLookupRowDo.java"
*/
export interface LookupRowDo<Key> extends DoEntity {
id: Key;
parentId: Key;
text: string;
tooltipText: string;
enabled: boolean;
active: boolean;
iconId: string;
cssClass: string;
additionalTableRowData: any;
}
/**
* @see "LookupResponse.java"
*/
export interface LookupResponse<TLookupRow = LookupRowDo<any>> extends DoEntity {
rows: TLookupRow[];
}