Skip to content

Commit f52aac8

Browse files
Change - Metadata field selector, add infinite scroll for data paginated (#3096)
* Change - Metadata field selector add infinite scroll for data paginated * Update change on new BRANCH * Fix - LINT ERRORS * Fix - LINT ERRORS
1 parent 08800f4 commit f52aac8

File tree

4 files changed

+124
-38
lines changed

4 files changed

+124
-38
lines changed

src/app/dso-shared/dso-edit-metadata/metadata-field-selector/metadata-field-selector.component.html

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,30 @@
66
[formControl]="input"
77
(focusin)="query$.next(mdField)"
88
(dsClickOutside)="query$.next(null)"
9-
(click)="$event.stopPropagation();" />
9+
(click)="$event.stopPropagation();"
10+
(keyup)="this.selectedValueLoading = false"
11+
/>
1012
<div class="invalid-feedback show-feedback" *ngIf="showInvalid">{{ dsoType + '.edit.metadata.metadatafield.invalid' | translate }}</div>
11-
<div class="autocomplete dropdown-menu" [ngClass]="{'show': (mdFieldOptions$ | async)?.length > 0}">
12-
<div class="dropdown-list">
13-
<div *ngFor="let mdFieldOption of (mdFieldOptions$ | async)">
14-
<button class="d-block dropdown-item" (click)="select(mdFieldOption)">
15-
<span [innerHTML]="mdFieldOption"></span>
13+
<div id="scrollable-metadata-field-selector" class="dropdown-menu scrollable-menu" [ngClass]="{'show': (mdFieldOptions$ | async)?.length > 0}">
14+
<div class="dropdown-list">
15+
<div
16+
infiniteScroll
17+
[infiniteScrollDistance]="1"
18+
[infiniteScrollThrottle]="0"
19+
[infiniteScrollContainer]="'#scrollable-metadata-field-selector'"
20+
[fromRoot]="true"
21+
(scrolled)="onScrollDown()">
22+
<ng-container *ngIf="mdFieldOptions$ | async">
23+
<button *ngFor="let listEntry of (mdFieldOptions$ | async)"
24+
class="d-block dropdown-item"
25+
dsHoverClass="ds-hover"
26+
(click)="select(listEntry)" #listEntryElement>
27+
<span [innerHTML]="listEntry"></span>
28+
</button>
29+
</ng-container>
30+
<button *ngIf="loading"
31+
class="list-group-item list-group-item-action border-0 list-entry">
32+
<ds-loading [showMessage]="false"></ds-loading>
1633
</button>
1734
</div>
1835
</div>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.scrollable-menu {
2+
height: auto;
3+
max-height: var(--ds-dso-selector-list-max-height);
4+
overflow: scroll;
5+
}

src/app/dso-shared/dso-edit-metadata/metadata-field-selector/metadata-field-selector.component.spec.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ describe('MetadataFieldSelectorComponent', () => {
3939
metadataSchema = Object.assign(new MetadataSchema(), {
4040
id: 0,
4141
prefix: 'dc',
42-
namespace: 'http://dublincore.org/documents/dcmi-terms/',
42+
namespace: 'https://schema.org/CreativeWork',
43+
field: '.',
4344
});
4445
metadataFields = [
4546
Object.assign(new MetadataField(), {
@@ -78,10 +79,10 @@ describe('MetadataFieldSelectorComponent', () => {
7879
});
7980

8081
describe('when a query is entered', () => {
81-
const query = 'test query';
82+
const query = 'dc.d';
8283

8384
beforeEach(() => {
84-
component.showInvalid = true;
85+
component.showInvalid = false;
8586
component.query$.next(query);
8687
});
8788

@@ -90,7 +91,7 @@ describe('MetadataFieldSelectorComponent', () => {
9091
});
9192

9293
it('should query the registry service for metadata fields and include the schema', () => {
93-
expect(registryService.queryMetadataFields).toHaveBeenCalledWith(query, { elementsPerPage: 10, sort: new SortOptions('fieldName', SortDirection.ASC) }, true, false, followLink('schema'));
94+
expect(registryService.queryMetadataFields).toHaveBeenCalledWith(query, { elementsPerPage: 20, sort: new SortOptions('fieldName', SortDirection.ASC), currentPage: 1 }, true, false, followLink('schema'));
9495
});
9596
});
9697

src/app/dso-shared/dso-edit-metadata/metadata-field-selector/metadata-field-selector.component.ts

Lines changed: 91 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,18 @@ import {
2424
TranslateModule,
2525
TranslateService,
2626
} from '@ngx-translate/core';
27+
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
2728
import {
2829
BehaviorSubject,
30+
combineLatest as observableCombineLatest,
2931
Observable,
3032
of,
3133
Subscription,
3234
} from 'rxjs';
3335
import {
3436
debounceTime,
35-
distinctUntilChanged,
3637
map,
38+
startWith,
3739
switchMap,
3840
take,
3941
tap,
@@ -50,6 +52,7 @@ import {
5052
metadataFieldsToString,
5153
} from '../../../core/shared/operators';
5254
import { hasValue } from '../../../shared/empty.util';
55+
import { ThemedLoadingComponent } from '../../../shared/loading/themed-loading.component';
5356
import { NotificationsService } from '../../../shared/notifications/notifications.service';
5457
import { ClickOutsideDirective } from '../../../shared/utils/click-outside.directive';
5558
import { followLink } from '../../../shared/utils/follow-link-config.model';
@@ -59,7 +62,7 @@ import { followLink } from '../../../shared/utils/follow-link-config.model';
5962
styleUrls: ['./metadata-field-selector.component.scss'],
6063
templateUrl: './metadata-field-selector.component.html',
6164
standalone: true,
62-
imports: [FormsModule, NgClass, ReactiveFormsModule, ClickOutsideDirective, NgIf, NgFor, AsyncPipe, TranslateModule],
65+
imports: [FormsModule, NgClass, ReactiveFormsModule, ClickOutsideDirective, NgIf, NgFor, AsyncPipe, TranslateModule, ThemedLoadingComponent, InfiniteScrollModule],
6366
})
6467
/**
6568
* Component displaying a searchable input for metadata-fields
@@ -96,7 +99,7 @@ export class MetadataFieldSelectorComponent implements OnInit, OnDestroy, AfterV
9699
* List of available metadata field options to choose from, dependent on the current query the user entered
97100
* Shows up in a dropdown below the input
98101
*/
99-
mdFieldOptions$: Observable<string[]>;
102+
mdFieldOptions$: BehaviorSubject<string[]> = new BehaviorSubject<string[]>([]);
100103

101104
/**
102105
* FormControl for the input
@@ -131,6 +134,30 @@ export class MetadataFieldSelectorComponent implements OnInit, OnDestroy, AfterV
131134
*/
132135
subs: Subscription[] = [];
133136

137+
138+
/**
139+
* The current page to load
140+
* Dynamically goes up as the user scrolls down until it reaches the last page possible
141+
*/
142+
currentPage$ = new BehaviorSubject(1);
143+
144+
/**
145+
* Whether or not the list contains a next page to load
146+
* This allows us to avoid next pages from trying to load when there are none
147+
*/
148+
hasNextPage = false;
149+
150+
/**
151+
* Whether or not new results are currently loading
152+
*/
153+
loading = false;
154+
155+
/**
156+
* Default page option for this feature
157+
*/
158+
pageOptions = { elementsPerPage: 20, sort: new SortOptions('fieldName', SortDirection.ASC) };
159+
160+
134161
constructor(protected registryService: RegistryService,
135162
protected notificationsService: NotificationsService,
136163
protected translate: TranslateService) {
@@ -141,32 +168,33 @@ export class MetadataFieldSelectorComponent implements OnInit, OnDestroy, AfterV
141168
* Update the mdFieldOptions$ depending on the query$ fired by querying the server
142169
*/
143170
ngOnInit(): void {
171+
this.subs.push(this.input.valueChanges.pipe(
172+
debounceTime(this.debounceTime),
173+
startWith(''),
174+
).subscribe((valueChange) => {
175+
this.currentPage$.next(1);
176+
if (!this.selectedValueLoading) {
177+
this.query$.next(valueChange);
178+
}
179+
this.mdField = valueChange;
180+
this.mdFieldChange.emit(this.mdField);
181+
}));
144182
this.subs.push(
145-
this.input.valueChanges.pipe(
146-
debounceTime(this.debounceTime),
147-
).subscribe((valueChange) => {
148-
if (!this.selectedValueLoading) {
149-
this.query$.next(valueChange);
150-
}
151-
this.selectedValueLoading = false;
152-
this.mdField = valueChange;
153-
this.mdFieldChange.emit(this.mdField);
154-
}),
155-
);
156-
this.mdFieldOptions$ = this.query$.pipe(
157-
distinctUntilChanged(),
158-
switchMap((query: string) => {
159-
this.showInvalid = false;
160-
if (query !== null) {
161-
return this.registryService.queryMetadataFields(query, { elementsPerPage: 10, sort: new SortOptions('fieldName', SortDirection.ASC) }, true, false, followLink('schema')).pipe(
162-
getAllSucceededRemoteData(),
163-
metadataFieldsToString(),
164-
);
165-
} else {
166-
return [[]];
167-
}
168-
}),
169-
);
183+
observableCombineLatest(
184+
this.query$,
185+
this.currentPage$,
186+
)
187+
.pipe(
188+
switchMap(([query, page]: [string, number]) => {
189+
this.loading = true;
190+
if (page === 1) {
191+
this.mdFieldOptions$.next([]);
192+
}
193+
return this.search(query as string, page as number);
194+
}),
195+
).subscribe((rd ) => {
196+
if (!this.selectedValueLoading) {this.updateList(rd);}
197+
}));
170198
}
171199

172200
/**
@@ -210,6 +238,41 @@ export class MetadataFieldSelectorComponent implements OnInit, OnDestroy, AfterV
210238
this.input.setValue(mdFieldOption);
211239
}
212240

241+
242+
/**
243+
* When the user reaches the bottom of the page (or almost) and there's a next page available, increase the current page
244+
*/
245+
onScrollDown() {
246+
if (this.hasNextPage && !this.loading) {
247+
this.currentPage$.next(this.currentPage$.value + 1);
248+
}
249+
}
250+
251+
/**
252+
* @Description It update the mdFieldOptions$ according the query result page
253+
* */
254+
updateList(list: string[]) {
255+
this.loading = false;
256+
this.hasNextPage = list.length > 0;
257+
const currentEntries = this.mdFieldOptions$.getValue();
258+
this.mdFieldOptions$.next([...currentEntries, ...list]);
259+
this.selectedValueLoading = false;
260+
}
261+
/**
262+
* Perform a search for the current query and page
263+
* @param query Query to search objects for
264+
* @param page Page to retrieve
265+
* @param useCache Whether or not to use the cache
266+
*/
267+
search(query: string, page: number, useCache: boolean = true) {
268+
return this.registryService.queryMetadataFields(query,{
269+
elementsPerPage: this.pageOptions.elementsPerPage, sort: this.pageOptions.sort,
270+
currentPage: page }, useCache, false, followLink('schema'))
271+
.pipe(
272+
getAllSucceededRemoteData(),
273+
metadataFieldsToString(),
274+
);
275+
}
213276
/**
214277
* Unsubscribe from any open subscriptions
215278
*/

0 commit comments

Comments
 (0)