Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change - Metadata field selector, add infinite scroll for data paginated #3096

Merged
merged 5 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,30 @@
[formControl]="input"
(focusin)="query$.next(mdField)"
(dsClickOutside)="query$.next(null)"
(click)="$event.stopPropagation();" />
(click)="$event.stopPropagation();"
(keyup)="this.selectedValueLoading = false"
/>
<div class="invalid-feedback show-feedback" *ngIf="showInvalid">{{ dsoType + '.edit.metadata.metadatafield.invalid' | translate }}</div>
<div class="autocomplete dropdown-menu" [ngClass]="{'show': (mdFieldOptions$ | async)?.length > 0}">
<div class="dropdown-list">
<div *ngFor="let mdFieldOption of (mdFieldOptions$ | async)">
<button class="d-block dropdown-item" (click)="select(mdFieldOption)">
<span [innerHTML]="mdFieldOption"></span>
<div id="scrollable-metadata-field-selector" class="dropdown-menu scrollable-menu" [ngClass]="{'show': (mdFieldOptions$ | async)?.length > 0}">
<div class="dropdown-list">
<div
infiniteScroll
[infiniteScrollDistance]="1"
[infiniteScrollThrottle]="0"
[infiniteScrollContainer]="'#scrollable-metadata-field-selector'"
[fromRoot]="true"
(scrolled)="onScrollDown()">
<ng-container *ngIf="mdFieldOptions$ | async">
<button *ngFor="let listEntry of (mdFieldOptions$ | async)"
class="d-block dropdown-item"
dsHoverClass="ds-hover"
(click)="select(listEntry)" #listEntryElement>
<span [innerHTML]="listEntry"></span>
</button>
</ng-container>
<button *ngIf="loading"
class="list-group-item list-group-item-action border-0 list-entry">
<ds-loading [showMessage]="false"></ds-loading>
</button>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.scrollable-menu {
height: auto;
max-height: var(--ds-dso-selector-list-max-height);
overflow: scroll;
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ describe('MetadataFieldSelectorComponent', () => {
metadataSchema = Object.assign(new MetadataSchema(), {
id: 0,
prefix: 'dc',
namespace: 'http://dublincore.org/documents/dcmi-terms/',
namespace: 'https://schema.org/CreativeWork',
field: '.',
});
metadataFields = [
Object.assign(new MetadataField(), {
Expand Down Expand Up @@ -78,10 +79,10 @@ describe('MetadataFieldSelectorComponent', () => {
});

describe('when a query is entered', () => {
const query = 'test query';
const query = 'dc.d';

beforeEach(() => {
component.showInvalid = true;
component.showInvalid = false;
component.query$.next(query);
});

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

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,18 @@
TranslateModule,
TranslateService,
} from '@ngx-translate/core';
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
import {
BehaviorSubject,
combineLatest as observableCombineLatest,
Observable,
of,
Subscription,
} from 'rxjs';
import {
debounceTime,
distinctUntilChanged,
map,
startWith,
switchMap,
take,
tap,
Expand All @@ -50,6 +52,7 @@
metadataFieldsToString,
} from '../../../core/shared/operators';
import { hasValue } from '../../../shared/empty.util';
import { ThemedLoadingComponent } from '../../../shared/loading/themed-loading.component';
import { NotificationsService } from '../../../shared/notifications/notifications.service';
import { ClickOutsideDirective } from '../../../shared/utils/click-outside.directive';
import { followLink } from '../../../shared/utils/follow-link-config.model';
Expand All @@ -59,7 +62,7 @@
styleUrls: ['./metadata-field-selector.component.scss'],
templateUrl: './metadata-field-selector.component.html',
standalone: true,
imports: [FormsModule, NgClass, ReactiveFormsModule, ClickOutsideDirective, NgIf, NgFor, AsyncPipe, TranslateModule],
imports: [FormsModule, NgClass, ReactiveFormsModule, ClickOutsideDirective, NgIf, NgFor, AsyncPipe, TranslateModule, ThemedLoadingComponent, InfiniteScrollModule],
})
/**
* Component displaying a searchable input for metadata-fields
Expand Down Expand Up @@ -96,7 +99,7 @@
* List of available metadata field options to choose from, dependent on the current query the user entered
* Shows up in a dropdown below the input
*/
mdFieldOptions$: Observable<string[]>;
mdFieldOptions$: BehaviorSubject<string[]> = new BehaviorSubject<string[]>([]);

/**
* FormControl for the input
Expand Down Expand Up @@ -131,6 +134,30 @@
*/
subs: Subscription[] = [];


/**
* The current page to load
* Dynamically goes up as the user scrolls down until it reaches the last page possible
*/
currentPage$ = new BehaviorSubject(1);

/**
* Whether or not the list contains a next page to load
* This allows us to avoid next pages from trying to load when there are none
*/
hasNextPage = false;

/**
* Whether or not new results are currently loading
*/
loading = false;

/**
* Default page option for this feature
*/
pageOptions = { elementsPerPage: 20, sort: new SortOptions('fieldName', SortDirection.ASC) };


constructor(protected registryService: RegistryService,
protected notificationsService: NotificationsService,
protected translate: TranslateService) {
Expand All @@ -141,32 +168,33 @@
* Update the mdFieldOptions$ depending on the query$ fired by querying the server
*/
ngOnInit(): void {
this.subs.push(this.input.valueChanges.pipe(
debounceTime(this.debounceTime),
startWith(''),
).subscribe((valueChange) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also add this subscribe to the subs array, so it can be unsubscribed onDestroy

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@artlowel I aded to subs array

this.currentPage$.next(1);
if (!this.selectedValueLoading) {
this.query$.next(valueChange);
}
this.mdField = valueChange;
this.mdFieldChange.emit(this.mdField);
}));
this.subs.push(
this.input.valueChanges.pipe(
debounceTime(this.debounceTime),
).subscribe((valueChange) => {
if (!this.selectedValueLoading) {
this.query$.next(valueChange);
}
this.selectedValueLoading = false;
this.mdField = valueChange;
this.mdFieldChange.emit(this.mdField);
}),
);
this.mdFieldOptions$ = this.query$.pipe(
distinctUntilChanged(),
switchMap((query: string) => {
this.showInvalid = false;
if (query !== null) {
return this.registryService.queryMetadataFields(query, { elementsPerPage: 10, sort: new SortOptions('fieldName', SortDirection.ASC) }, true, false, followLink('schema')).pipe(
getAllSucceededRemoteData(),
metadataFieldsToString(),
);
} else {
return [[]];
}
}),
);
observableCombineLatest(
this.query$,
this.currentPage$,
)
.pipe(
switchMap(([query, page]: [string, number]) => {
this.loading = true;
if (page === 1) {
this.mdFieldOptions$.next([]);
}
return this.search(query as string, page as number);
}),
).subscribe((rd ) => {
if (!this.selectedValueLoading) {this.updateList(rd);}
}));
}

/**
Expand Down Expand Up @@ -210,6 +238,41 @@
this.input.setValue(mdFieldOption);
}


/**
* When the user reaches the bottom of the page (or almost) and there's a next page available, increase the current page
*/
onScrollDown() {
if (this.hasNextPage && !this.loading) {
this.currentPage$.next(this.currentPage$.value + 1);

Check warning on line 247 in src/app/dso-shared/dso-edit-metadata/metadata-field-selector/metadata-field-selector.component.ts

View check run for this annotation

Codecov / codecov/patch

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

Added line #L247 was not covered by tests
}
}

/**
* @Description It update the mdFieldOptions$ according the query result page
* */
updateList(list: string[]) {
this.loading = false;
this.hasNextPage = list.length > 0;
const currentEntries = this.mdFieldOptions$.getValue();
this.mdFieldOptions$.next([...currentEntries, ...list]);
this.selectedValueLoading = false;
}
/**
* Perform a search for the current query and page
* @param query Query to search objects for
* @param page Page to retrieve
* @param useCache Whether or not to use the cache
*/
search(query: string, page: number, useCache: boolean = true) {
return this.registryService.queryMetadataFields(query,{
elementsPerPage: this.pageOptions.elementsPerPage, sort: this.pageOptions.sort,
currentPage: page }, useCache, false, followLink('schema'))
.pipe(
getAllSucceededRemoteData(),
metadataFieldsToString(),
);
}
/**
* Unsubscribe from any open subscriptions
*/
Expand Down
Loading