@intellegens/cornerstone-client 0.0.9999-alpha-19 → 0.0.9999-alpha-21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,32 +1,49 @@
1
1
  import { IIdentifiable } from '../../data';
2
- export declare class SearchAdapter<TKey, TDto extends IIdentifiable<TKey>> {
2
+ export type SearchAdapterOptions = {
3
+ controllerName: string;
4
+ typesToSearch: string[];
5
+ multiselect: boolean;
6
+ searchTriggerMinLength: number;
7
+ searchDebounceDelay: number;
8
+ limitSearchToSelectedType: boolean;
9
+ resultsLimit: number;
10
+ };
11
+ declare class SingularEventTarget<T> {
12
+ private _target;
13
+ addEventListener(callback: EventListenerOrEventListenerObject, options?: AddEventListenerOptions | boolean): void;
14
+ dispatchEvent(value: T): void;
15
+ }
16
+ export interface IGlobalSearchable<TKey> extends IIdentifiable<TKey> {
17
+ type: string;
18
+ }
19
+ export declare class SearchAdapter<TKey, TDto extends IGlobalSearchable<TKey>> {
3
20
  private _readClient;
21
+ private _options;
4
22
  private _isLoading;
23
+ private _currentAbortController?;
24
+ constructor(options: SearchAdapterOptions);
25
+ onChange: SingularEventTarget<string>;
26
+ private _emitChange;
27
+ get multiselect(): boolean;
28
+ private _searchText;
29
+ get searchText(): string;
30
+ set searchText(searchText: string);
5
31
  private _searchResults;
6
- private _totalItemCount;
7
- private _searchTerm;
8
- private _selectedType?;
9
- private _lastInputSearchTerm?;
10
- private _lastInputType?;
11
- private _searchTriggerLength;
12
- private _searchTriggerDelay;
13
- private _triggeredSearch;
14
- private _selection;
15
- private _multiSelectMode;
16
- constructor(controllerName: string, multiSelect: boolean, searchTriggerLength: number, searchTriggerDelay: number, dataChangedCallback: (isLoading: boolean, data: TDto[] | undefined, error: string | undefined) => void);
17
- getSelectedItems(): TDto[];
18
- getSelectedType(): string | undefined;
19
- getSelectMode(): boolean;
32
+ get searchResults(): TDto[];
33
+ protected set searchResults(searchResults: TDto[]);
34
+ private _selectedItems;
35
+ get selectedItems(): TDto[];
36
+ set selectedItems(selectedItems: TDto[]);
20
37
  addToSelection(item: TDto): void;
21
38
  removeFromSelection(item: TDto): void;
22
- search(searchTerm: string, type?: string): Promise<TDto[]>;
23
- clearSearch(): void;
24
- private readonly _dataChangedCallback;
25
- private _fetchCurrentPageDataTimeout?;
26
- private _fetchCurrentPageDataPromises;
27
- private _currentAbortController?;
28
- private _fetchCurrentPageData;
29
- private _fetchCurrentPageDataDebounced;
39
+ private _lastSearchedValue;
40
+ private _typesToSearch;
41
+ private _trySearch;
42
+ private _fetchResultsDataTimeout?;
43
+ private _fetchResultsPromises;
44
+ private _fetchResults;
45
+ private _fetchResultsDebounced;
30
46
  private _parseToSearchDefinition;
31
47
  private _sortByName;
32
48
  }
49
+ export {};
@@ -1,144 +1,122 @@
1
1
  import { ReadSelectedComparisonOperator, ReadSelectedLogicalOperator, ReadSelectedOrderingDirection, ReadSelectedPropertyType, } from '../../data';
2
2
  import { ApiReadControllerClient } from '../../services';
3
+ class SingularEventTarget {
4
+ _target = new EventTarget();
5
+ addEventListener(callback, options) {
6
+ this._target.addEventListener('change', callback, options);
7
+ }
8
+ dispatchEvent(value) {
9
+ this._target.dispatchEvent(new CustomEvent('change', { detail: value }));
10
+ }
11
+ }
3
12
  export class SearchAdapter {
13
+ // inputs
4
14
  _readClient;
15
+ _options;
5
16
  _isLoading = false;
17
+ _currentAbortController;
18
+ constructor(options) {
19
+ this._options = options;
20
+ this._readClient = new ApiReadControllerClient(options.controllerName);
21
+ }
22
+ onChange = new SingularEventTarget();
23
+ _emitChange() {
24
+ this.onChange.dispatchEvent('onChange');
25
+ }
26
+ get multiselect() {
27
+ return this._options.multiselect;
28
+ }
29
+ _searchText = '';
30
+ get searchText() {
31
+ return this._searchText;
32
+ }
33
+ set searchText(searchText) {
34
+ this._searchText = searchText;
35
+ this._trySearch();
36
+ this._emitChange();
37
+ }
6
38
  _searchResults = [];
7
- _totalItemCount = 1000;
8
- _searchTerm = '';
9
- _selectedType;
10
- _lastInputSearchTerm;
11
- _lastInputType;
12
- _searchTriggerLength;
13
- _searchTriggerDelay;
14
- _triggeredSearch = false;
15
- _selection;
16
- _multiSelectMode = true;
17
- constructor(controllerName, multiSelect, searchTriggerLength, searchTriggerDelay, dataChangedCallback) {
18
- this._readClient = new ApiReadControllerClient(controllerName);
19
- this._searchTriggerLength = searchTriggerLength;
20
- this._searchTriggerDelay = searchTriggerDelay;
21
- this._multiSelectMode = multiSelect;
22
- this._dataChangedCallback = dataChangedCallback;
23
- this._fetchCurrentPageData();
24
- }
25
- //#region Public methods
26
- getSelectedItems() {
27
- return this._selection || [];
28
- }
29
- getSelectedType() {
30
- return this._selectedType;
31
- }
32
- getSelectMode() {
33
- return this._multiSelectMode;
39
+ get searchResults() {
40
+ return this._searchResults;
41
+ }
42
+ set searchResults(searchResults) {
43
+ this._searchResults = [...searchResults];
44
+ this._emitChange();
45
+ }
46
+ _selectedItems = [];
47
+ get selectedItems() {
48
+ return this._selectedItems;
49
+ }
50
+ set selectedItems(selectedItems) {
51
+ this._selectedItems = [...selectedItems];
52
+ this._emitChange();
34
53
  }
35
54
  addToSelection(item) {
36
- if (!this._selection) {
37
- this._selection = [];
55
+ if (!this._selectedItems) {
56
+ this.selectedItems = [];
38
57
  }
39
- if (this._multiSelectMode) {
40
- if (!this._selection.find(s => s.id === item.id)) {
41
- this._selection.push(item);
58
+ // selection mode
59
+ if (this._options.multiselect) {
60
+ if (!this._selectedItems.find(s => s.id === item.id)) {
61
+ this.selectedItems = [...this.selectedItems, item];
42
62
  }
43
63
  }
44
64
  else {
45
65
  // single-select mode - replace the item and return the old one to shown results
46
- if (this._selection.length > 0) {
47
- this._searchResults = [...this._searchResults, ...this._selection];
66
+ if (this._selectedItems.length > 0) {
67
+ this.searchResults = this._sortByName([...this._searchResults, ...this._selectedItems]); // return the to-be-replaced selected item back to shown results list
48
68
  }
49
- this._selection = [item];
69
+ this.selectedItems = [item];
50
70
  }
51
- this._searchResults = this._searchResults.filter(d => d.id !== item.id);
52
- this._dataChangedCallback(this._isLoading, this._sortByName(this._searchResults), undefined);
71
+ this.searchResults = this._sortByName(this._searchResults.filter(d => d.id !== item.id)); // remove the new selection from the shown results
53
72
  }
54
73
  removeFromSelection(item) {
55
- if (!this._selection) {
56
- this._selection = [];
74
+ if (!this._selectedItems) {
75
+ this.selectedItems = [];
57
76
  return;
58
77
  }
59
- this._selection = this._selection.filter(s => s.id !== item.id);
60
- this._searchResults = [...this._searchResults, item];
61
- this._dataChangedCallback(this._isLoading, this._sortByName(this._searchResults), undefined);
62
- }
63
- search(searchTerm, type) {
64
- if (this._lastInputSearchTerm === searchTerm && this._lastInputType === type) {
65
- // no changes to search condition
66
- return Promise.resolve(this._searchResults);
67
- }
68
- this._lastInputSearchTerm = searchTerm;
69
- this._lastInputType = type;
70
- if (type !== undefined && type !== null) {
71
- // has type selected -> single-select mode
72
- if (searchTerm.length >= this._searchTriggerLength) {
73
- // include search by string
74
- this._searchTerm = searchTerm;
75
- this._selectedType = type;
76
- this._triggeredSearch = true;
77
- return this._fetchCurrentPageData();
78
- }
79
- else {
80
- // search all by type
81
- this._searchTerm = '';
82
- this._selectedType = type;
83
- this._triggeredSearch = true;
84
- return this._fetchCurrentPageData();
85
- }
86
- }
87
- else if (searchTerm.length >= this._searchTriggerLength) {
88
- // no type involved -> search by input string only
89
- this._searchTerm = searchTerm;
90
- this._selectedType = type;
91
- this._triggeredSearch = true;
92
- return this._fetchCurrentPageData();
93
- }
94
- else if (this._triggeredSearch && searchTerm.length < this._searchTriggerLength) {
95
- // user has deleted part of the search term after having seen some results - fetch everything to show
96
- this._searchTerm = '';
97
- this._selectedType = type;
98
- return this._fetchCurrentPageData();
99
- }
100
- else {
101
- // no type, searchTerm < this._searchTriggerLength
102
- return Promise.resolve([]);
103
- }
78
+ this.selectedItems = this._selectedItems.filter(s => s.id !== item.id);
79
+ this.searchResults = this._sortByName([...this._searchResults, item]);
104
80
  }
105
- clearSearch() {
106
- this._selection = [];
107
- this._selectedType = undefined;
108
- this._searchTerm = '';
109
- this._dataChangedCallback(this._isLoading, [], undefined);
110
- this._triggeredSearch = false;
111
- }
112
- //#endregion
113
- //#region Private methods
114
- _dataChangedCallback;
115
- _fetchCurrentPageDataTimeout;
116
- _fetchCurrentPageDataPromises = [];
117
- _currentAbortController;
118
- async _fetchCurrentPageData() {
81
+ _lastSearchedValue = '';
82
+ _typesToSearch = [];
83
+ async _trySearch() {
84
+ const options = this._options;
85
+ this.searchResults = [];
86
+ if (this._searchText.length < options.searchTriggerMinLength)
87
+ return;
88
+ if (this._searchText == this._lastSearchedValue)
89
+ return;
90
+ this._lastSearchedValue = this._searchText;
91
+ this._typesToSearch =
92
+ options.limitSearchToSelectedType && this._selectedItems.length ? [this._selectedItems[0].type] : this._options.typesToSearch;
93
+ this.searchResults = this._sortByName(await this._fetchResults(options.searchDebounceDelay));
94
+ }
95
+ _fetchResultsDataTimeout;
96
+ _fetchResultsPromises = [];
97
+ async _fetchResults(debounceDelay) {
119
98
  return new Promise((resolve, reject) => {
120
- this._fetchCurrentPageDataPromises.push({ resolve, reject });
121
- if (this._fetchCurrentPageDataTimeout !== undefined) {
122
- clearTimeout(this._fetchCurrentPageDataTimeout);
99
+ this._fetchResultsPromises.push({ resolve, reject });
100
+ if (this._fetchResultsDataTimeout !== undefined) {
101
+ clearTimeout(this._fetchResultsDataTimeout);
123
102
  }
124
- this._fetchCurrentPageDataTimeout = setTimeout(async () => {
103
+ this._fetchResultsDataTimeout = setTimeout(async () => {
125
104
  try {
126
- const result = await this._fetchCurrentPageDataDebounced();
127
- const promises = this._fetchCurrentPageDataPromises.splice(0, this._fetchCurrentPageDataPromises.length);
105
+ const result = await this._fetchResultsDebounced();
106
+ const promises = this._fetchResultsPromises.splice(0, this._fetchResultsPromises.length);
128
107
  for (const p of promises)
129
108
  p.resolve(result);
130
109
  }
131
110
  catch (err) {
132
- const promises = this._fetchCurrentPageDataPromises.splice(0, this._fetchCurrentPageDataPromises.length);
111
+ const promises = this._fetchResultsPromises.splice(0, this._fetchResultsPromises.length);
133
112
  for (const p of promises)
134
113
  p.reject(err);
135
114
  }
136
- }, this._searchTriggerDelay);
115
+ }, debounceDelay);
137
116
  });
138
117
  }
139
- async _fetchCurrentPageDataDebounced() {
118
+ async _fetchResultsDebounced() {
140
119
  this._isLoading = true;
141
- this._dataChangedCallback(this._isLoading, undefined, undefined);
142
120
  let abortController;
143
121
  let caughtError;
144
122
  try {
@@ -158,10 +136,9 @@ export class SearchAdapter {
158
136
  if (!response.ok) {
159
137
  throw response.error;
160
138
  }
161
- // Filter out selected items from results
162
- const selectedIds = new Set((this._selection || []).map(item => item.id));
163
- this._searchResults = response.result.filter(item => !selectedIds.has(item.id));
164
- return this._searchResults;
139
+ // Filter out selected items from new results
140
+ const selectedIds = new Set((this.selectedItems || []).map(item => item.id));
141
+ return response.result.filter(item => !selectedIds.has(item.id));
165
142
  }
166
143
  catch (error) {
167
144
  caughtError = error;
@@ -178,7 +155,6 @@ export class SearchAdapter {
178
155
  if (caughtError && !(caughtError instanceof DOMException && caughtError.name === 'AbortError')) {
179
156
  errorName = caughtError instanceof Error ? caughtError.name : undefined;
180
157
  }
181
- this._dataChangedCallback(this._isLoading, this._searchResults, errorName);
182
158
  // Clear the abort controller only if it belongs to this request (avoid racing with a newer one)
183
159
  if (abortController && this._currentAbortController === abortController) {
184
160
  this._currentAbortController = undefined;
@@ -189,7 +165,7 @@ export class SearchAdapter {
189
165
  const definition = {
190
166
  paginationDefinition: {
191
167
  skip: 0,
192
- limit: this._totalItemCount,
168
+ limit: this._options.resultsLimit,
193
169
  },
194
170
  };
195
171
  // Apply ordering
@@ -207,21 +183,26 @@ export class SearchAdapter {
207
183
  propertyName: 'Name',
208
184
  comparisonOperator: ReadSelectedComparisonOperator.IContains,
209
185
  valueType: ReadSelectedPropertyType.String,
210
- value: this._searchTerm,
186
+ value: this._searchText,
211
187
  },
212
188
  ];
213
- if (this._selectedType !== undefined) {
214
- propertyCriteria.push({
215
- propertyName: 'Type',
216
- comparisonOperator: ReadSelectedComparisonOperator.Equal,
217
- valueType: ReadSelectedPropertyType.String,
218
- value: this._selectedType,
219
- });
220
- }
221
189
  definition.searchDefinition = {
222
190
  logicalOperator: ReadSelectedLogicalOperator.And,
223
191
  propertyCriteria: propertyCriteria,
224
192
  };
193
+ if (this._typesToSearch !== undefined && this._typesToSearch.length > 0) {
194
+ definition.searchDefinition.searches = [
195
+ {
196
+ logicalOperator: ReadSelectedLogicalOperator.Or,
197
+ propertyCriteria: this._typesToSearch.map(type => ({
198
+ propertyName: 'Type',
199
+ comparisonOperator: ReadSelectedComparisonOperator.IEqual,
200
+ valueType: ReadSelectedPropertyType.String,
201
+ value: type,
202
+ })),
203
+ },
204
+ ];
205
+ }
225
206
  return definition;
226
207
  }
227
208
  _sortByName(array) {
@@ -0,0 +1,7 @@
1
+ import { IIdentifiable } from "../..";
2
+ export interface ICommonIdentifiable<TKey> extends IIdentifiable<TKey> {
3
+ /**
4
+ * Gets or sets the type of the entity.
5
+ */
6
+ type: string;
7
+ }
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intellegens/cornerstone-client",
3
- "version": "0.0.9999-alpha-19",
3
+ "version": "0.0.9999-alpha-21",
4
4
  "private": false,
5
5
  "publishable": true,
6
6
  "main": "./dist/index.js",
@@ -10,174 +10,157 @@ import {
10
10
  } from '@data';
11
11
  import { ApiReadControllerClient } from '@services';
12
12
 
13
- export class SearchAdapter<TKey, TDto extends IIdentifiable<TKey>> {
13
+ export type SearchAdapterOptions = {
14
+ controllerName: string;
15
+ typesToSearch: string[]; // provide known searchable types
16
+ multiselect: boolean;
17
+ searchTriggerMinLength: number;
18
+ searchDebounceDelay: number;
19
+ limitSearchToSelectedType: boolean; // when item is selected, limit results to items of same type
20
+ resultsLimit: number;
21
+ };
22
+
23
+ class SingularEventTarget<T> {
24
+ private _target = new EventTarget();
25
+
26
+ public addEventListener(callback: EventListenerOrEventListenerObject, options?: AddEventListenerOptions | boolean): void {
27
+ this._target.addEventListener('change', callback, options);
28
+ }
29
+
30
+ public dispatchEvent(value: T) {
31
+ this._target.dispatchEvent(new CustomEvent('change', { detail: value }));
32
+ }
33
+ }
34
+
35
+ export interface IGlobalSearchable<TKey> extends IIdentifiable<TKey> {
36
+ type: string;
37
+ }
38
+
39
+ export class SearchAdapter<TKey, TDto extends IGlobalSearchable<TKey>> {
40
+ // inputs
14
41
  private _readClient!: ApiReadControllerClient<TKey, TDto, TDto>;
42
+ private _options!: SearchAdapterOptions;
15
43
 
16
44
  private _isLoading = false;
17
- private _searchResults: TDto[] = [];
18
- private _totalItemCount: number = 1000;
19
-
20
- private _searchTerm: string = '';
21
- private _selectedType?: string;
22
- private _lastInputSearchTerm?: string;
23
- private _lastInputType?: string;
24
-
25
- private _searchTriggerLength!: number;
26
- private _searchTriggerDelay!: number;
27
- private _triggeredSearch = false;
28
-
29
- private _selection!: TDto[];
30
- private _multiSelectMode = true;
31
-
32
- constructor(
33
- controllerName: string,
34
- multiSelect: boolean,
35
- searchTriggerLength: number,
36
- searchTriggerDelay: number,
37
- dataChangedCallback: (isLoading: boolean, data: TDto[] | undefined, error: string | undefined) => void,
38
- ) {
39
- this._readClient = new ApiReadControllerClient<TKey, TDto, TDto>(controllerName);
40
-
41
- this._searchTriggerLength = searchTriggerLength;
42
- this._searchTriggerDelay = searchTriggerDelay;
43
- this._multiSelectMode = multiSelect;
44
-
45
- this._dataChangedCallback = dataChangedCallback;
46
- this._fetchCurrentPageData();
45
+ private _currentAbortController?: AbortController;
46
+
47
+ constructor(options: SearchAdapterOptions) {
48
+ this._options = options;
49
+ this._readClient = new ApiReadControllerClient<TKey, TDto, TDto>(options.controllerName);
47
50
  }
48
51
 
49
- //#region Public methods
52
+ public onChange = new SingularEventTarget<string>();
53
+ private _emitChange() {
54
+ this.onChange.dispatchEvent('onChange');
55
+ }
50
56
 
51
- public getSelectedItems(): TDto[] {
52
- return this._selection || [];
57
+ public get multiselect() {
58
+ return this._options.multiselect;
53
59
  }
54
60
 
55
- public getSelectedType(): string | undefined {
56
- return this._selectedType;
61
+ private _searchText: string = '';
62
+ public get searchText() {
63
+ return this._searchText;
64
+ }
65
+ public set searchText(searchText: string) {
66
+ this._searchText = searchText;
67
+ this._trySearch();
68
+ this._emitChange();
57
69
  }
58
70
 
59
- public getSelectMode(): boolean {
60
- return this._multiSelectMode;
71
+ private _searchResults: TDto[] = [];
72
+ public get searchResults() {
73
+ return this._searchResults;
74
+ }
75
+ protected set searchResults(searchResults: TDto[]) {
76
+ this._searchResults = [...searchResults];
77
+ this._emitChange();
78
+ }
79
+
80
+ private _selectedItems: TDto[] = [];
81
+ public get selectedItems() {
82
+ return this._selectedItems;
83
+ }
84
+ public set selectedItems(selectedItems: TDto[]) {
85
+ this._selectedItems = [...selectedItems];
86
+ this._emitChange();
61
87
  }
62
88
 
63
89
  public addToSelection(item: TDto): void {
64
- if (!this._selection) {
65
- this._selection = [];
90
+ if (!this._selectedItems) {
91
+ this.selectedItems = [];
66
92
  }
67
93
 
68
- if (this._multiSelectMode) {
69
- if (!this._selection.find(s => s.id === item.id)) {
70
- this._selection.push(item);
94
+ // selection mode
95
+ if (this._options.multiselect) {
96
+ if (!this._selectedItems.find(s => s.id === item.id)) {
97
+ this.selectedItems = [...this.selectedItems, item];
71
98
  }
72
99
  } else {
73
100
  // single-select mode - replace the item and return the old one to shown results
74
- if (this._selection.length > 0) {
75
- this._searchResults = [...this._searchResults, ...this._selection];
101
+ if (this._selectedItems.length > 0) {
102
+ this.searchResults = this._sortByName([...this._searchResults, ...this._selectedItems]); // return the to-be-replaced selected item back to shown results list
76
103
  }
77
- this._selection = [item];
104
+ this.selectedItems = [item];
78
105
  }
79
- this._searchResults = this._searchResults.filter(d => d.id !== item.id);
80
- this._dataChangedCallback(this._isLoading, this._sortByName(this._searchResults), undefined);
106
+ this.searchResults = this._sortByName(this._searchResults.filter(d => d.id !== item.id)); // remove the new selection from the shown results
81
107
  }
82
108
 
83
109
  public removeFromSelection(item: TDto): void {
84
- if (!this._selection) {
85
- this._selection = [];
110
+ if (!this._selectedItems) {
111
+ this.selectedItems = [];
86
112
  return;
87
113
  }
88
114
 
89
- this._selection = this._selection.filter(s => s.id !== item.id);
90
- this._searchResults = [...this._searchResults, item];
91
-
92
- this._dataChangedCallback(this._isLoading, this._sortByName(this._searchResults), undefined);
115
+ this.selectedItems = this._selectedItems.filter(s => s.id !== item.id);
116
+ this.searchResults = this._sortByName([...this._searchResults, item]);
93
117
  }
94
118
 
95
- public search(searchTerm: string, type?: string) {
96
- if (this._lastInputSearchTerm === searchTerm && this._lastInputType === type) {
97
- // no changes to search condition
98
- return Promise.resolve(this._searchResults);
99
- }
100
-
101
- this._lastInputSearchTerm = searchTerm;
102
- this._lastInputType = type;
103
-
104
- if (type !== undefined && type !== null) {
105
- // has type selected -> single-select mode
106
- if (searchTerm.length >= this._searchTriggerLength) {
107
- // include search by string
108
- this._searchTerm = searchTerm;
109
- this._selectedType = type;
110
- this._triggeredSearch = true;
111
- return this._fetchCurrentPageData();
112
- } else {
113
- // search all by type
114
- this._searchTerm = '';
115
- this._selectedType = type;
116
- this._triggeredSearch = true;
117
- return this._fetchCurrentPageData();
118
- }
119
- } else if (searchTerm.length >= this._searchTriggerLength) {
120
- // no type involved -> search by input string only
121
- this._searchTerm = searchTerm;
122
- this._selectedType = type;
123
- this._triggeredSearch = true;
124
- return this._fetchCurrentPageData();
125
- } else if (this._triggeredSearch && searchTerm.length < this._searchTriggerLength) {
126
- // user has deleted part of the search term after having seen some results - fetch everything to show
127
- this._searchTerm = '';
128
- this._selectedType = type;
129
- return this._fetchCurrentPageData();
130
- } else {
131
- // no type, searchTerm < this._searchTriggerLength
132
- return Promise.resolve([]);
133
- }
134
- }
119
+ private _lastSearchedValue = '';
120
+ private _typesToSearch: string[] = [];
135
121
 
136
- public clearSearch(): void {
137
- this._selection = [];
138
- this._selectedType = undefined;
139
- this._searchTerm = '';
140
- this._dataChangedCallback(this._isLoading, [], undefined);
141
- this._triggeredSearch = false;
142
- }
122
+ private async _trySearch() {
123
+ const options = this._options;
143
124
 
144
- //#endregion
125
+ this.searchResults = [];
126
+ if (this._searchText.length < options.searchTriggerMinLength) return;
127
+ if (this._searchText == this._lastSearchedValue) return;
128
+ this._lastSearchedValue = this._searchText;
145
129
 
146
- //#region Private methods
130
+ this._typesToSearch =
131
+ options.limitSearchToSelectedType && this._selectedItems.length ? [(this._selectedItems[0] as TDto).type] : this._options.typesToSearch;
147
132
 
148
- private readonly _dataChangedCallback!: (isLoading: boolean, data: TDto[] | undefined, errorMessage: string | undefined) => void;
133
+ this.searchResults = this._sortByName(await this._fetchResults(options.searchDebounceDelay));
134
+ }
149
135
 
150
- private _fetchCurrentPageDataTimeout?: any;
136
+ private _fetchResultsDataTimeout?: any;
151
137
 
152
- private _fetchCurrentPageDataPromises: {
138
+ private _fetchResultsPromises: {
153
139
  resolve: (value: TDto[] | PromiseLike<TDto[]>) => void;
154
140
  reject: (reason?: unknown) => void;
155
141
  }[] = [];
156
142
 
157
- private _currentAbortController?: AbortController;
158
-
159
- private async _fetchCurrentPageData(): Promise<TDto[]> {
143
+ private async _fetchResults(debounceDelay: number): Promise<TDto[]> {
160
144
  return new Promise((resolve, reject) => {
161
- this._fetchCurrentPageDataPromises.push({ resolve, reject });
162
- if (this._fetchCurrentPageDataTimeout !== undefined) {
163
- clearTimeout(this._fetchCurrentPageDataTimeout);
145
+ this._fetchResultsPromises.push({ resolve, reject });
146
+ if (this._fetchResultsDataTimeout !== undefined) {
147
+ clearTimeout(this._fetchResultsDataTimeout);
164
148
  }
165
- this._fetchCurrentPageDataTimeout = setTimeout(async () => {
149
+ this._fetchResultsDataTimeout = setTimeout(async () => {
166
150
  try {
167
- const result = await this._fetchCurrentPageDataDebounced();
168
- const promises = this._fetchCurrentPageDataPromises.splice(0, this._fetchCurrentPageDataPromises.length);
151
+ const result = await this._fetchResultsDebounced();
152
+ const promises = this._fetchResultsPromises.splice(0, this._fetchResultsPromises.length);
169
153
  for (const p of promises) p.resolve(result);
170
154
  } catch (err) {
171
- const promises = this._fetchCurrentPageDataPromises.splice(0, this._fetchCurrentPageDataPromises.length);
155
+ const promises = this._fetchResultsPromises.splice(0, this._fetchResultsPromises.length);
172
156
  for (const p of promises) p.reject(err);
173
157
  }
174
- }, this._searchTriggerDelay);
158
+ }, debounceDelay);
175
159
  });
176
160
  }
177
161
 
178
- private async _fetchCurrentPageDataDebounced(): Promise<TDto[]> {
162
+ private async _fetchResultsDebounced(): Promise<TDto[]> {
179
163
  this._isLoading = true;
180
- this._dataChangedCallback(this._isLoading, undefined, undefined);
181
164
  let abortController: AbortController | undefined;
182
165
  let caughtError: unknown;
183
166
 
@@ -200,11 +183,9 @@ export class SearchAdapter<TKey, TDto extends IIdentifiable<TKey>> {
200
183
  throw response.error;
201
184
  }
202
185
 
203
- // Filter out selected items from results
204
- const selectedIds = new Set((this._selection || []).map(item => item.id));
205
- this._searchResults = response.result.filter(item => !selectedIds.has(item.id));
206
-
207
- return this._searchResults;
186
+ // Filter out selected items from new results
187
+ const selectedIds = new Set((this.selectedItems || []).map(item => item.id));
188
+ return response.result.filter(item => !selectedIds.has(item.id));
208
189
  } catch (error: unknown) {
209
190
  caughtError = error;
210
191
 
@@ -222,10 +203,8 @@ export class SearchAdapter<TKey, TDto extends IIdentifiable<TKey>> {
222
203
  if (caughtError && !(caughtError instanceof DOMException && caughtError.name === 'AbortError')) {
223
204
  errorName = caughtError instanceof Error ? caughtError.name : undefined;
224
205
  }
225
- this._dataChangedCallback(this._isLoading, this._searchResults, errorName);
226
206
 
227
207
  // Clear the abort controller only if it belongs to this request (avoid racing with a newer one)
228
-
229
208
  if (abortController && this._currentAbortController === abortController) {
230
209
  this._currentAbortController = undefined;
231
210
  }
@@ -236,7 +215,7 @@ export class SearchAdapter<TKey, TDto extends IIdentifiable<TKey>> {
236
215
  const definition: ReadSelectedDefinitionDto<TDto> = {
237
216
  paginationDefinition: {
238
217
  skip: 0,
239
- limit: this._totalItemCount,
218
+ limit: this._options.resultsLimit,
240
219
  },
241
220
  };
242
221
 
@@ -256,24 +235,32 @@ export class SearchAdapter<TKey, TDto extends IIdentifiable<TKey>> {
256
235
  propertyName: 'Name' as any,
257
236
  comparisonOperator: ReadSelectedComparisonOperator.IContains,
258
237
  valueType: ReadSelectedPropertyType.String,
259
- value: this._searchTerm,
238
+ value: this._searchText,
260
239
  },
261
240
  ];
262
241
 
263
- if (this._selectedType !== undefined) {
264
- propertyCriteria.push({
265
- propertyName: 'Type',
266
- comparisonOperator: ReadSelectedComparisonOperator.Equal,
267
- valueType: ReadSelectedPropertyType.String,
268
- value: this._selectedType,
269
- } as ReadSelectedSearchPropertyDefinitionDto<TDto, keyof TDto>);
270
- }
271
-
272
242
  definition.searchDefinition = {
273
243
  logicalOperator: ReadSelectedLogicalOperator.And,
274
244
  propertyCriteria: propertyCriteria,
275
245
  } as ReadSelectedSearchDefinitionDto<TDto>;
276
246
 
247
+ if (this._typesToSearch !== undefined && this._typesToSearch.length > 0) {
248
+ definition.searchDefinition.searches = [
249
+ {
250
+ logicalOperator: ReadSelectedLogicalOperator.Or,
251
+ propertyCriteria: this._typesToSearch.map(
252
+ type =>
253
+ ({
254
+ propertyName: 'Type',
255
+ comparisonOperator: ReadSelectedComparisonOperator.IEqual,
256
+ valueType: ReadSelectedPropertyType.String,
257
+ value: type,
258
+ }) as ReadSelectedSearchPropertyDefinitionDto<TDto, keyof TDto>,
259
+ ),
260
+ },
261
+ ];
262
+ }
263
+
277
264
  return definition;
278
265
  }
279
266
 
@@ -284,6 +271,4 @@ export class SearchAdapter<TKey, TDto extends IIdentifiable<TKey>> {
284
271
  return nameA.localeCompare(nameB);
285
272
  });
286
273
  }
287
-
288
- //#endregion
289
274
  }
@@ -0,0 +1,9 @@
1
+ import {IIdentifiable} from "@data";
2
+
3
+
4
+ export interface ICommonIdentifiable<TKey> extends IIdentifiable<TKey> {
5
+ /**
6
+ * Gets or sets the type of the entity.
7
+ */
8
+ type: string;
9
+ }