UNPKG

@eclipse-scout/core

Version:
270 lines (233 loc) 8.16 kB
/* * Copyright (c) 2010, 2024 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, HierarchicalLookupResultBuilder, InitModelOf, LookupCall, LookupResult, LookupRow, objects, QueryBy, scout, StaticLookupCallModel, strings} from '../index'; import $ from 'jquery'; /** * Base class for lookup calls with static or local data. Implement the _data() and _dataToLookupRow() * functions to provide data for lookup calls. Results are resolved as a Promise, the delay * property controls how long it takes until the promise is resolved. You can set it to a higher value for testing purposes. */ export class StaticLookupCall<TKey> extends LookupCall<TKey> implements StaticLookupCallModel<TKey> { declare model: StaticLookupCallModel<TKey>; delay: number; data: any[]; protected _deferred: JQuery.Deferred<LookupResult<TKey>>; constructor() { super(); this._deferred = null; this.delay = 0; this.data = null; this.active = true; } protected override _init(model: InitModelOf<this>) { super._init(model); if (!this.data) { // data may either be provided by the model or by implementing the _data function this.data = this._data(); } } refreshData(data?: any[]) { if (data === undefined) { this.data = this._data(); } else { this.data = data; } } override abort() { this._deferred?.reject({ abort: true }); super.abort(); } protected override _getAll(): JQuery.Promise<LookupResult<TKey>> { this._deferred = $.Deferred(); setTimeout(this._queryByAll.bind(this), this.delay); return this._deferred.promise(); } protected _queryByAll() { this._deferred.resolve({ queryBy: QueryBy.ALL, lookupRows: this._lookupRowsByAll() }); } /** * Limits the data to 'maxRowCount' results, converts the data to LookupRows and filters * the rows by their 'active' state. If a subclass overrides this method it can omit the * super call, if it must avoid the maxRowCount limit and does its own filtering. In that * case the subclass may choose to call <code>#_mapAndFilterData</code> and * <code>#_limitMaxRows</code> when suitable. */ protected _lookupRowsByAll(): LookupRow<TKey>[] { let datas = this._limitMaxRows(this.data); return this._mapAndFilterData(datas); } protected _limitMaxRows(array: any[]): any[] { return arrays.ensure(array).slice(0, this.maxRowCount); } protected _mapAndFilterData(datas: any[]): LookupRow<TKey>[] { return datas .map(this._dataToLookupRow, this) .filter(this._filterActiveLookupRow, this); } protected _filterActiveLookupRow(dataRow: LookupRow<TKey>): boolean { if (objects.isNullOrUndefined(this.active)) { return true; } return this.active === scout.nvl(dataRow.active, true); } protected override _getByText(text: string): JQuery.Promise<LookupResult<TKey>> { this._deferred = $.Deferred(); setTimeout(this._queryByText.bind(this, text), this.delay); return this._deferred.promise(); } protected _queryByText(text: string) { let lookupRows = this._lookupRowsByText(text); // resolve non-hierarchical results immediately if (!this.hierarchical) { this._deferred.resolve({ queryBy: QueryBy.TEXT, text: text, lookupRows: lookupRows }); } // if loadIncremental=false we must also load children let promise, builder = new HierarchicalLookupResultBuilder(this); if (this.loadIncremental) { promise = $.resolvedPromise(lookupRows); } else { promise = builder.addChildLookupRows(lookupRows); } // hierarchical lookups must first load their parent nodes // before we can resolve the results promise .then(lookupRows => builder.addParentLookupRows(lookupRows)) .done(lookupRows => { this._deferred.resolve({ queryBy: QueryBy.TEXT, text: text, lookupRows: lookupRows }); }) .fail(error => { throw error; }); } protected _lookupRowsByText(text: string): LookupRow<TKey>[] { let regex = this._createSearchPattern(text); let datas = this.data.filter(data => regex.test(data[1].toLowerCase())); return this._mapAndFilterData(datas); } _createSearchPattern(text: string): RegExp { // Implementation copied from LocalLookupCall.java const WILDCARD = '*'; const WILDCARD_PLACEHOLDER = '@wildcard@'; text = strings.nvl(text); text = text.toLowerCase(); text = text.replace(new RegExp(strings.quote(WILDCARD), 'g'), WILDCARD_PLACEHOLDER); text = strings.quote(text); // replace repeating wildcards to prevent regex DoS let duplicateWildcards = WILDCARD_PLACEHOLDER + WILDCARD_PLACEHOLDER; while (strings.contains(text, duplicateWildcards)) { text = text.replace(duplicateWildcards, WILDCARD_PLACEHOLDER); } if (!strings.endsWith(text, WILDCARD_PLACEHOLDER)) { text += WILDCARD_PLACEHOLDER; } text = text.replace(new RegExp(strings.quote(WILDCARD_PLACEHOLDER), 'g'), '.*'); return new RegExp('^' + text + '$', 's'); // s = DOT_ALL } protected override _getByKey(key: TKey): JQuery.Promise<LookupResult<TKey>> { this._deferred = $.Deferred(); setTimeout(this._queryByKey.bind(this, key), this.delay); return this._deferred.promise(); } protected _queryByKey(key: TKey) { let lookupRow = this._lookupRowByKey(key); if (lookupRow) { this._deferred.resolve({ queryBy: QueryBy.KEY, lookupRows: [lookupRow] }); } else { this._deferred.reject(); } } protected override _getByKeys(keys: TKey[]): JQuery.Promise<LookupResult<TKey>> { this._deferred = $.Deferred(); setTimeout(() => this._queryByKeys(keys), this.delay); return this._deferred.promise(); } protected _queryByKeys(keys: TKey[]) { const lookupRows = arrays.ensure(keys).map(key => this._lookupRowByKey(key)).filter(row => !!row); if (lookupRows.length) { this._deferred.resolve({ queryBy: QueryBy.KEY, lookupRows }); } else { this._deferred.reject(); } } protected _lookupRowByKey(key: TKey): LookupRow<TKey> { let data = arrays.find(this.data, data => data[0] === key); if (!data) { return null; } return this._dataToLookupRow(data); } protected override _getByRec(rec: TKey): JQuery.Promise<LookupResult<TKey>> { this._deferred = $.Deferred(); setTimeout(this._queryByRec.bind(this, rec), this.delay); return this._deferred.promise(); } protected _queryByRec(rec: TKey) { this._deferred.resolve({ queryBy: QueryBy.REC, rec: rec, lookupRows: this._lookupRowsByRec(rec) }); } protected _lookupRowsByRec(rec: TKey): LookupRow<TKey>[] { return this.data.reduce((aggr, data) => { if (data[2] === rec) { aggr.push(this._dataToLookupRow(data)); } return aggr; }, []) .filter(this._filterActiveLookupRow, this); } setDelay(delay: number) { this.delay = delay; } /** * Implement this function to convert a single data array into an instance of LookupRow. */ protected _dataToLookupRow(data: any[], index?: number): LookupRow<TKey> { return scout.create(LookupRow, { key: data[0], text: data[1], parentKey: data[2] }) as LookupRow<TKey>; } /** * Implement this function to provide static data. * * The result is expected to be a list of tuples. Each tuple will be converted to a lookup row via * {@link _dataToLookupRow}. The first three elements of each tuple have a predefined meaning and * are used by the lookup call (e.g. to filter rows by text). More elements can be added freely. * * - [0] = key * - [1] = text * - [2] = parentKey */ protected _data(): any[] { return []; } }