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 1 commit
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-themed-loading [showMessage]="false"></ds-themed-loading>
tdonohue marked this conversation as resolved.
Show resolved Hide resolved
</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 @@ -28,7 +28,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 @@ -68,10 +69,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 @@ -80,7 +81,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 @@ -9,7 +9,13 @@ import {
Output,
ViewChild
} from '@angular/core';
import { debounceTime, distinctUntilChanged, map, switchMap, take, tap } from 'rxjs/operators';
import {
debounceTime,
map, startWith,
switchMap,
take,
tap
} from 'rxjs/operators';
import { followLink } from '../../../shared/utils/follow-link-config.model';
import {
getAllSucceededRemoteData,
Expand All @@ -22,10 +28,11 @@ import { UntypedFormControl } from '@angular/forms';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { hasValue } from '../../../shared/empty.util';
import { Subscription } from 'rxjs/internal/Subscription';
import { of } from 'rxjs/internal/observable/of';
import { of } from 'rxjs';
import { NotificationsService } from '../../../shared/notifications/notifications.service';
import { TranslateService } from '@ngx-translate/core';
import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model';
import { combineLatest as observableCombineLatest } from 'rxjs';

@Component({
selector: 'ds-metadata-field-selector',
Expand Down Expand Up @@ -67,7 +74,7 @@ export class MetadataFieldSelectorComponent implements OnInit, OnDestroy, AfterV
* 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 @@ -102,6 +109,30 @@ export class MetadataFieldSelectorComponent implements OnInit, OnDestroy, AfterV
*/
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 @@ -112,32 +143,33 @@ export class MetadataFieldSelectorComponent implements OnInit, OnDestroy, AfterV
* Update the mdFieldOptions$ depending on the query$ fired by querying the server
*/
ngOnInit(): void {
this.input.valueChanges.pipe(
debounceTime(this.debounceTime),
startWith(''),
tap( () => this.currentPage$.next(1) )
Copy link
Member

Choose a reason for hiding this comment

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

It's not a good idea to "perform work" in a tap. Taps are meant for debugging purposes, and shouldn't affect anything else. The main reason for that is that the code in the tap won't be executed if nothing is subscribed to the pipe in the end. Since you're subscribing immediately underneath this line that's not an issue here, but then there's also no good reason not to move this code to the subscribe as well.

Copy link
Contributor

Choose a reason for hiding this comment

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

@artlowel Hi, I changed this observable in the subscribe function

).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

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, page);
})
).subscribe((rd ) => {
if (!this.selectedValueLoading) {this.updateList(rd);}
}));
}

/**
Expand Down Expand Up @@ -181,6 +213,41 @@ export class MetadataFieldSelectorComponent implements OnInit, OnDestroy, AfterV
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);
}
}

/**
* @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
2 changes: 2 additions & 0 deletions src/app/dso-shared/dso-shared.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import { DsoEditMetadataValueComponent } from './dso-edit-metadata/dso-edit-meta
import { DsoEditMetadataHeadersComponent } from './dso-edit-metadata/dso-edit-metadata-headers/dso-edit-metadata-headers.component';
import { DsoEditMetadataValueHeadersComponent } from './dso-edit-metadata/dso-edit-metadata-value-headers/dso-edit-metadata-value-headers.component';
import { ThemedDsoEditMetadataComponent } from './dso-edit-metadata/themed-dso-edit-metadata.component';
import { InfiniteScrollModule } from 'ngx-infinite-scroll';

@NgModule({
imports: [
SharedModule,
InfiniteScrollModule
],
declarations: [
DsoEditMetadataComponent,
Expand Down
Loading