Skip to content

Commit fbe4ce5

Browse files
authored
Merge pull request #3824 from tdonohue/port_3096_to_7x
[Port dspace-7_x] Change - Metadata field selector, add infinite scroll for data paginated
2 parents 58e9a60 + 05d5a08 commit fbe4ce5

File tree

4 files changed

+128
-42
lines changed

4 files changed

+128
-42
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
@@ -28,7 +28,8 @@ describe('MetadataFieldSelectorComponent', () => {
2828
metadataSchema = Object.assign(new MetadataSchema(), {
2929
id: 0,
3030
prefix: 'dc',
31-
namespace: 'http://dublincore.org/documents/dcmi-terms/',
31+
namespace: 'https://schema.org/CreativeWork',
32+
field: '.',
3233
});
3334
metadataFields = [
3435
Object.assign(new MetadataField(), {
@@ -68,10 +69,10 @@ describe('MetadataFieldSelectorComponent', () => {
6869
});
6970

7071
describe('when a query is entered', () => {
71-
const query = 'test query';
72+
const query = 'dc.d';
7273

7374
beforeEach(() => {
74-
component.showInvalid = true;
75+
component.showInvalid = false;
7576
component.query$.next(query);
7677
});
7778

@@ -80,7 +81,7 @@ describe('MetadataFieldSelectorComponent', () => {
8081
});
8182

8283
it('should query the registry service for metadata fields and include the schema', () => {
83-
expect(registryService.queryMetadataFields).toHaveBeenCalledWith(query, { elementsPerPage: 10, sort: new SortOptions('fieldName', SortDirection.ASC) }, true, false, followLink('schema'));
84+
expect(registryService.queryMetadataFields).toHaveBeenCalledWith(query, { elementsPerPage: 20, sort: new SortOptions('fieldName', SortDirection.ASC), currentPage: 1 }, true, false, followLink('schema'));
8485
});
8586
});
8687

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

Lines changed: 95 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -9,28 +9,31 @@ import {
99
Output,
1010
ViewChild
1111
} from '@angular/core';
12-
import { debounceTime, distinctUntilChanged, map, switchMap, take, tap } from 'rxjs/operators';
12+
import { debounceTime, map, startWith, switchMap, take, tap } from 'rxjs/operators';
1313
import { followLink } from '../../../shared/utils/follow-link-config.model';
1414
import {
1515
getAllSucceededRemoteData,
1616
getFirstCompletedRemoteData,
1717
metadataFieldsToString
1818
} from '../../../core/shared/operators';
19-
import { Observable } from 'rxjs/internal/Observable';
19+
import {
20+
BehaviorSubject,
21+
combineLatest as observableCombineLatest,
22+
Observable,
23+
of,
24+
Subscription,
25+
} from 'rxjs';
2026
import { RegistryService } from '../../../core/registry/registry.service';
2127
import { UntypedFormControl } from '@angular/forms';
22-
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
2328
import { hasValue } from '../../../shared/empty.util';
24-
import { Subscription } from 'rxjs/internal/Subscription';
25-
import { of } from 'rxjs/internal/observable/of';
2629
import { NotificationsService } from '../../../shared/notifications/notifications.service';
2730
import { TranslateService } from '@ngx-translate/core';
2831
import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model';
2932

3033
@Component({
3134
selector: 'ds-metadata-field-selector',
3235
styleUrls: ['./metadata-field-selector.component.scss'],
33-
templateUrl: './metadata-field-selector.component.html'
36+
templateUrl: './metadata-field-selector.component.html',
3437
})
3538
/**
3639
* Component displaying a searchable input for metadata-fields
@@ -67,7 +70,7 @@ export class MetadataFieldSelectorComponent implements OnInit, OnDestroy, AfterV
6770
* List of available metadata field options to choose from, dependent on the current query the user entered
6871
* Shows up in a dropdown below the input
6972
*/
70-
mdFieldOptions$: Observable<string[]>;
73+
mdFieldOptions$: BehaviorSubject<string[]> = new BehaviorSubject<string[]>([]);
7174

7275
/**
7376
* FormControl for the input
@@ -102,6 +105,30 @@ export class MetadataFieldSelectorComponent implements OnInit, OnDestroy, AfterV
102105
*/
103106
subs: Subscription[] = [];
104107

108+
109+
/**
110+
* The current page to load
111+
* Dynamically goes up as the user scrolls down until it reaches the last page possible
112+
*/
113+
currentPage$ = new BehaviorSubject(1);
114+
115+
/**
116+
* Whether or not the list contains a next page to load
117+
* This allows us to avoid next pages from trying to load when there are none
118+
*/
119+
hasNextPage = false;
120+
121+
/**
122+
* Whether or not new results are currently loading
123+
*/
124+
loading = false;
125+
126+
/**
127+
* Default page option for this feature
128+
*/
129+
pageOptions = { elementsPerPage: 20, sort: new SortOptions('fieldName', SortDirection.ASC) };
130+
131+
105132
constructor(protected registryService: RegistryService,
106133
protected notificationsService: NotificationsService,
107134
protected translate: TranslateService) {
@@ -112,32 +139,33 @@ export class MetadataFieldSelectorComponent implements OnInit, OnDestroy, AfterV
112139
* Update the mdFieldOptions$ depending on the query$ fired by querying the server
113140
*/
114141
ngOnInit(): void {
142+
this.subs.push(this.input.valueChanges.pipe(
143+
debounceTime(this.debounceTime),
144+
startWith(''),
145+
).subscribe((valueChange) => {
146+
this.currentPage$.next(1);
147+
if (!this.selectedValueLoading) {
148+
this.query$.next(valueChange);
149+
}
150+
this.mdField = valueChange;
151+
this.mdFieldChange.emit(this.mdField);
152+
}));
115153
this.subs.push(
116-
this.input.valueChanges.pipe(
117-
debounceTime(this.debounceTime),
118-
).subscribe((valueChange) => {
119-
if (!this.selectedValueLoading) {
120-
this.query$.next(valueChange);
121-
}
122-
this.selectedValueLoading = false;
123-
this.mdField = valueChange;
124-
this.mdFieldChange.emit(this.mdField);
125-
}),
126-
);
127-
this.mdFieldOptions$ = this.query$.pipe(
128-
distinctUntilChanged(),
129-
switchMap((query: string) => {
130-
this.showInvalid = false;
131-
if (query !== null) {
132-
return this.registryService.queryMetadataFields(query, { elementsPerPage: 10, sort: new SortOptions('fieldName', SortDirection.ASC) }, true, false, followLink('schema')).pipe(
133-
getAllSucceededRemoteData(),
134-
metadataFieldsToString(),
135-
);
136-
} else {
137-
return [[]];
138-
}
139-
}),
140-
);
154+
observableCombineLatest(
155+
this.query$,
156+
this.currentPage$,
157+
)
158+
.pipe(
159+
switchMap(([query, page]: [string, number]) => {
160+
this.loading = true;
161+
if (page === 1) {
162+
this.mdFieldOptions$.next([]);
163+
}
164+
return this.search(query as string, page as number);
165+
}),
166+
).subscribe((rd ) => {
167+
if (!this.selectedValueLoading) {this.updateList(rd);}
168+
}));
141169
}
142170

143171
/**
@@ -181,6 +209,41 @@ export class MetadataFieldSelectorComponent implements OnInit, OnDestroy, AfterV
181209
this.input.setValue(mdFieldOption);
182210
}
183211

212+
213+
/**
214+
* When the user reaches the bottom of the page (or almost) and there's a next page available, increase the current page
215+
*/
216+
onScrollDown() {
217+
if (this.hasNextPage && !this.loading) {
218+
this.currentPage$.next(this.currentPage$.value + 1);
219+
}
220+
}
221+
222+
/**
223+
* @Description It update the mdFieldOptions$ according the query result page
224+
* */
225+
updateList(list: string[]) {
226+
this.loading = false;
227+
this.hasNextPage = list.length > 0;
228+
const currentEntries = this.mdFieldOptions$.getValue();
229+
this.mdFieldOptions$.next([...currentEntries, ...list]);
230+
this.selectedValueLoading = false;
231+
}
232+
/**
233+
* Perform a search for the current query and page
234+
* @param query Query to search objects for
235+
* @param page Page to retrieve
236+
* @param useCache Whether or not to use the cache
237+
*/
238+
search(query: string, page: number, useCache: boolean = true) {
239+
return this.registryService.queryMetadataFields(query,{
240+
elementsPerPage: this.pageOptions.elementsPerPage, sort: this.pageOptions.sort,
241+
currentPage: page }, useCache, false, followLink('schema'))
242+
.pipe(
243+
getAllSucceededRemoteData(),
244+
metadataFieldsToString(),
245+
);
246+
}
184247
/**
185248
* Unsubscribe from any open subscriptions
186249
*/

0 commit comments

Comments
 (0)