diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.html
index 1ac38e9943c..ca854ae9a2c 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.html
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.html
@@ -44,8 +44,8 @@
{{'form.loading' | translate}}
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.ts
index a4ca2101934..d02bb88261f 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.ts
@@ -40,6 +40,9 @@ export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyCom
public loading = false;
public pageInfo: PageInfo;
public optionsList: any;
+ public allOptionsList: any;
+ public inputText = '';
+ public acceptableKeys = ['Space', 'NumpadMultiply', 'NumpadAdd', 'NumpadSubtract', 'NumpadDecimal', 'Semicolon', 'Equal', 'Comma', 'Minus', 'Period', 'Quote', 'Backquote'];
constructor(protected vocabularyService: VocabularyService,
protected cdr: ChangeDetectorRef,
@@ -53,7 +56,8 @@ export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyCom
* Initialize the component, setting up the init form value
*/
ngOnInit() {
- this.updatePageInfo(this.model.maxOptions, 1);
+ // Obtain ALL the entries from the vocabulary, needed to search for the first matching option
+ this.updatePageInfo(9999, 1);
this.vocabularyService.getVocabularyEntries(this.model.vocabularyOptions, this.pageInfo).pipe(
getFirstSucceededRemoteDataPayload(),
catchError(() => observableOf(buildPaginatedList(
@@ -62,7 +66,9 @@ export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyCom
))
))
.subscribe((list: PaginatedList) => {
- this.optionsList = list.page;
+ // Save all entries, slice the first page to display, we only load more entries when scrolling / searching
+ this.allOptionsList = list.page;
+ this.optionsList = list.page.slice(0, this.model.maxOptions);
if (this.model.value) {
this.setCurrentValue(this.model.value, true);
}
@@ -113,6 +119,42 @@ export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyCom
} else if (keyName === 'ArrowDown' || keyName === 'ArrowUp') {
this.openDropdown(sdRef);
}
+
+ if (keyName === 'Backspace') {
+ this.inputText = this.inputText.slice(0, -1);
+ } else if (this.isAcceptableKey(keyName)) {
+ this.inputText += keyName;
+ }
+
+ const matchingOption = this.findMatchingOption(this.allOptionsList, this.inputText);
+
+ if (matchingOption) {
+ // the matching option is not in the list of options to display, so we need to load more options
+ // TODO: Not sure if at this point it's even worth it to paginate the dropdown options
+ const maxAttempts = 3;
+ const waitForOption = async () => {
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
+ if (this.optionsList.includes(matchingOption)) {
+ this.scrollToMatchingOption(matchingOption.display);
+ break;
+ }
+ this.pageInfo.currentPage++;
+ this.optionsList = this.allOptionsList.slice(0, this.pageInfo.elementsPerPage * this.pageInfo.currentPage);
+ await new Promise(resolve => setTimeout(resolve, 100));
+ }
+ };
+ waitForOption();
+ } else {
+ this.setCurrentValue(null);
+ }
+ }
+
+ findMatchingOption(options: any, inputText: string): any {
+ if (isEmpty(inputText)) {
+ return null;
+ }
+ return options.find(option => option.display.toLowerCase().startsWith(this.inputText.toLowerCase())
+ );
}
/**
@@ -121,30 +163,9 @@ export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyCom
onScroll() {
if (!this.loading && this.pageInfo.currentPage <= this.pageInfo.totalPages) {
this.loading = true;
- this.updatePageInfo(
- this.pageInfo.elementsPerPage,
- this.pageInfo.currentPage + 1,
- this.pageInfo.totalElements,
- this.pageInfo.totalPages
- );
- this.vocabularyService.getVocabularyEntries(this.model.vocabularyOptions, this.pageInfo).pipe(
- getFirstSucceededRemoteDataPayload(),
- catchError(() => observableOf(buildPaginatedList(
- new PageInfo(),
- []
- ))
- ),
- tap(() => this.loading = false))
- .subscribe((list: PaginatedList) => {
- this.optionsList = this.optionsList.concat(list.page);
- this.updatePageInfo(
- list.pageInfo.elementsPerPage,
- list.pageInfo.currentPage,
- list.pageInfo.totalElements,
- list.pageInfo.totalPages
- );
- this.cdr.detectChanges();
- });
+ this.pageInfo.currentPage++;
+ this.optionsList = this.allOptionsList.slice(0, this.pageInfo.elementsPerPage * this.pageInfo.currentPage);
+ this.loading = false;
}
}
@@ -183,4 +204,30 @@ export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyCom
this.currentValue = result;
}
+ scrollToMatchingOption(matchingOption: any): void {
+ console.log('scrollToMatchingOption', matchingOption);
+ const element = document.getElementById('option-' + matchingOption.replace(/ /g,"-"));
+ console.log('element', element.id);
+ if (element) {
+ // remove active class from all options
+ const activeElements = document.getElementsByClassName('active');
+ for (let i = 0; i < activeElements.length; i++) {
+ activeElements[i].classList.remove('active');
+ }
+ // scroll to the matching option and center it in the dropdown
+ element.scrollIntoView({block: 'center'});
+ // add active class to the matching option
+ element.className += ' active';
+ this.setCurrentValue(matchingOption);
+ }
+ }
+
+ isAcceptableKey(keyPress: string): boolean {
+ // allow all letters and numbers
+ if (keyPress.length == 1 && keyPress.match(/^[a-zA-Z0-9]*$/)) {
+ return true;
+ }
+ return this.acceptableKeys.includes(keyPress);
+ }
+
}