diff --git a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html
index 1a35ed525c3..600e0f40772 100644
--- a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html
+++ b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html
@@ -139,3 +139,20 @@
+
+@if ((showPreviousPage$ | async) || (showNextPage$ | async)) {
+
+ @if (showPreviousPage$ | async) {
+
+ }
+ @if (showNextPage$ | async) {
+
+ }
+
+}
diff --git a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.ts b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.ts
index 9c44057e62b..f2eb8ea9a4f 100644
--- a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.ts
+++ b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.ts
@@ -34,6 +34,7 @@ import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
import { TranslateModule } from '@ngx-translate/core';
import {
Observable,
+ of,
Subscription,
} from 'rxjs';
import {
@@ -168,6 +169,10 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit, OnChanges
readonly AlertType = AlertType;
+ public showNextPage$: Observable;
+
+ public showPreviousPage$: Observable;
+
/**
* Initialize instance variables
*
@@ -270,6 +275,12 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit, OnChanges
* Initialize the component, setting up the data to build the tree
*/
ngOnInit(): void {
+
+ // Initialize observables to false when component loads
+ // Ensures pagination buttons are hidden on first load or after navigation
+ this.showNextPage$ = of(false);
+ this.showPreviousPage$ = of(false);
+
this.subs.push(
this.vocabularyService.findVocabularyById(this.vocabularyOptions.name).pipe(
// Retrieve the configured preloadLevel from REST
@@ -339,6 +350,17 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit, OnChanges
* Search for a vocabulary entry by query
*/
search() {
+
+ // Reassign observables after performing each new search
+ // Updates pagination button visibility based on available pages
+ this.showNextPage$ = this.vocabularyTreeviewService.showNextPageSubject
+ ? this.vocabularyTreeviewService.showNextPageSubject.asObservable()
+ : of(false);
+
+ this.showPreviousPage$ = this.vocabularyTreeviewService.showPreviousPageSubject
+ ? this.vocabularyTreeviewService.showPreviousPageSubject.asObservable()
+ : of(false);
+
if (isNotEmpty(this.searchText)) {
if (isEmpty(this.storedNodeMap)) {
this.storedNodeMap = this.nodeMap;
@@ -348,6 +370,30 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit, OnChanges
}
}
+ /**
+ * Loads the next page of vocabulary search results.
+ * Increments the current page in the service and re-triggers the query with the same search term and selection.
+ */
+ loadNextPage(): void {
+ const svc = this.vocabularyTreeviewService;
+
+ if (svc.currentPage < svc.totalPages) {
+ svc.searchByQueryAndPage(svc.queryInProgress, [], svc.currentPage + 1);
+ }
+ }
+
+ /**
+ * Loads the previous page of vocabulary search results.
+ * Decrements the current page in the service and re-triggers the query with the same search term and selection.
+ */
+ loadPreviousPage(): void {
+ const svc = this.vocabularyTreeviewService;
+
+ if (svc.currentPage > 1) {
+ svc.searchByQueryAndPage(svc.queryInProgress, [], svc.currentPage - 1);
+ }
+ }
+
/**
* Check if search box contains any text
*/
@@ -374,6 +420,9 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit, OnChanges
if (this.searchInput) {
this.searchInput.nativeElement.focus();
}
+
+ this.showNextPage$ = of(false);
+ this.showPreviousPage$ = of(false);
}
add() {
diff --git a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.service.ts b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.service.ts
index 21efec92825..5a958c615ed 100644
--- a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.service.ts
+++ b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.service.ts
@@ -1,6 +1,8 @@
import { Injectable } from '@angular/core';
import { PaginatedList } from '@dspace/core/data/paginated-list.model';
+import { RemoteData } from '@dspace/core/data/remote-data';
import {
+ getFirstSucceededRemoteData,
getFirstSucceededRemoteDataPayload,
getFirstSucceededRemoteListPayload,
} from '@dspace/core/shared/operators';
@@ -24,6 +26,7 @@ import {
merge,
mergeMap,
scan,
+ tap,
} from 'rxjs/operators';
import {
@@ -90,6 +93,12 @@ export class VocabularyTreeviewService {
*/
private hideSearchingWhenUnsubscribed$ = new Observable(() => () => this.loading.next(false));
+ public currentPage = 1;
+ public totalPages = 1;
+ public queryInProgress = '';
+ public showNextPageSubject = new BehaviorSubject(false);
+ public showPreviousPageSubject = new BehaviorSubject(false);
+
/**
* Initialize instance variables
*
@@ -197,10 +206,28 @@ export class VocabularyTreeviewService {
}
/**
- * Perform a search operation by query
+ * Initiates a vocabulary search using the provided query term and selection, starting from the first page.
+ *
+ * @param query - The text input to search for within the vocabulary.
+ * @param selectedItems - Currently selected vocabulary item IDs to retain in the result.
*/
searchByQuery(query: string, selectedItems: string[]) {
+ this.searchByQueryAndPage(query, selectedItems, 1);
+ }
+
+ /**
+ * Executes a paginated vocabulary search with the given query, selection, and page number.
+ * Updates pagination state, loading indicators, and triggers the vocabulary tree rebuild.
+ *
+ * @param query - The search term to filter vocabulary entries.
+ * @param selectedItems - IDs of items currently selected in the tree.
+ * @param page - The page number to fetch (1-based index).
+ */
+ searchByQueryAndPage(query: string, selectedItems: string[], page: number = 1) {
this.loading.next(true);
+ this.queryInProgress = query;
+ this.currentPage = page;
+
if (isEmpty(this.storedNodes)) {
this.storedNodes = this.dataChange.value;
this.storedNodeMap = this.nodeMap;
@@ -208,9 +235,22 @@ export class VocabularyTreeviewService {
this.nodeMap = new Map();
this.dataChange.next([]);
- this.vocabularyService.getVocabularyEntriesByValue(query, false, this.vocabularyOptions, new PageInfo()).pipe(
+ const pageInfo = new PageInfo({
+ elementsPerPage: 20,
+ currentPage: page,
+ totalElements: 0,
+ totalPages: 0,
+ });
+
+ this.vocabularyService.getVocabularyEntriesByValue(query, false, this.vocabularyOptions, pageInfo).pipe(
+ getFirstSucceededRemoteData(),
+ tap((rd: RemoteData>) => {
+ this.totalPages = rd.payload.pageInfo.totalPages;
+ this.showPreviousPageSubject.next(rd.payload.pageInfo.currentPage > 1);
+ this.showNextPageSubject.next(rd.payload.pageInfo.currentPage < this.totalPages);
+ }),
getFirstSucceededRemoteListPayload(),
- mergeMap((result: VocabularyEntry[]) => (result.length > 0) ? result : of(null)),
+ mergeMap((result: VocabularyEntry[]) => result.length > 0 ? result : of(null)),
mergeMap((entry: VocabularyEntry) =>
this.vocabularyService.findEntryDetailById(entry.otherInformation.id, this.vocabularyName).pipe(
getFirstSucceededRemoteDataPayload(),
diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5
index c0b620d24d7..8895f042ed2 100644
--- a/src/assets/i18n/en.json5
+++ b/src/assets/i18n/en.json5
@@ -1127,6 +1127,10 @@
"browse.title": "Browsing by {{ field }}{{ startsWith }} {{ value }}",
+ "browse.taxonomy.show_next_results": "Show next results",
+
+ "browse.taxonomy.show_previous_results": "Show previous results",
+
"browse.title.page": "Browsing by {{ field }} {{ value }}",
"search.browse.item-back": "Back to Results",