UNPKG

@eclipse-scout/core

Version:
273 lines (235 loc) 9.1 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 {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[]; }