Skip to content

Commit a162499

Browse files
authored
Merge pull request #5029 from DSpace/backport-4508-to-dspace-9_x
[Port dspace-9_x] Add paginated vocabulary search with 'Show previous/next results' controls in hierarchical browse
2 parents ff2844e + 88f4aa3 commit a162499

4 files changed

Lines changed: 113 additions & 3 deletions

File tree

src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,20 @@ <h2 class="h4 text-center text-muted mt-4" >
139139
</cdk-tree-node>
140140
</cdk-tree>
141141
</div>
142+
<!-- Show previous/next results pagination -->
143+
@if ((showPreviousPage$ | async) || (showNextPage$ | async)) {
144+
<div class="mb-3 d-flex gap-2">
145+
@if (showPreviousPage$ | async) {
146+
<button class="btn btn-outline-secondary"
147+
(click)="loadPreviousPage()">
148+
{{ 'browse.taxonomy.show_previous_results' | translate }}
149+
</button>
150+
}
151+
@if (showNextPage$ | async) {
152+
<button class="btn btn-outline-secondary"
153+
(click)="loadNextPage()">
154+
{{ 'browse.taxonomy.show_next_results' | translate }}
155+
</button>
156+
}
157+
</div>
158+
}

src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap';
2020
import { TranslateModule } from '@ngx-translate/core';
2121
import {
2222
Observable,
23+
of,
2324
Subscription,
2425
} from 'rxjs';
2526
import {
@@ -167,6 +168,10 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit, OnChanges
167168

168169
readonly AlertType = AlertType;
169170

171+
public showNextPage$: Observable<boolean>;
172+
173+
public showPreviousPage$: Observable<boolean>;
174+
170175
/**
171176
* Initialize instance variables
172177
*
@@ -270,6 +275,12 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit, OnChanges
270275
* Initialize the component, setting up the data to build the tree
271276
*/
272277
ngOnInit(): void {
278+
279+
// Initialize observables to false when component loads
280+
// Ensures pagination buttons are hidden on first load or after navigation
281+
this.showNextPage$ = of(false);
282+
this.showPreviousPage$ = of(false);
283+
273284
this.subs.push(
274285
this.vocabularyService.findVocabularyById(this.vocabularyOptions.name).pipe(
275286
// Retrieve the configured preloadLevel from REST
@@ -339,6 +350,17 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit, OnChanges
339350
* Search for a vocabulary entry by query
340351
*/
341352
search() {
353+
354+
// Reassign observables after performing each new search
355+
// Updates pagination button visibility based on available pages
356+
this.showNextPage$ = this.vocabularyTreeviewService.showNextPageSubject
357+
? this.vocabularyTreeviewService.showNextPageSubject.asObservable()
358+
: of(false);
359+
360+
this.showPreviousPage$ = this.vocabularyTreeviewService.showPreviousPageSubject
361+
? this.vocabularyTreeviewService.showPreviousPageSubject.asObservable()
362+
: of(false);
363+
342364
if (isNotEmpty(this.searchText)) {
343365
if (isEmpty(this.storedNodeMap)) {
344366
this.storedNodeMap = this.nodeMap;
@@ -348,6 +370,30 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit, OnChanges
348370
}
349371
}
350372

373+
/**
374+
* Loads the next page of vocabulary search results.
375+
* Increments the current page in the service and re-triggers the query with the same search term and selection.
376+
*/
377+
loadNextPage(): void {
378+
const svc = this.vocabularyTreeviewService;
379+
380+
if (svc.currentPage < svc.totalPages) {
381+
svc.searchByQueryAndPage(svc.queryInProgress, [], svc.currentPage + 1);
382+
}
383+
}
384+
385+
/**
386+
* Loads the previous page of vocabulary search results.
387+
* Decrements the current page in the service and re-triggers the query with the same search term and selection.
388+
*/
389+
loadPreviousPage(): void {
390+
const svc = this.vocabularyTreeviewService;
391+
392+
if (svc.currentPage > 1) {
393+
svc.searchByQueryAndPage(svc.queryInProgress, [], svc.currentPage - 1);
394+
}
395+
}
396+
351397
/**
352398
* Check if search box contains any text
353399
*/
@@ -374,6 +420,9 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit, OnChanges
374420
if (this.searchInput) {
375421
this.searchInput.nativeElement.focus();
376422
}
423+
424+
this.showNextPage$ = of(false);
425+
this.showPreviousPage$ = of(false);
377426
}
378427

379428
add() {

src/app/shared/form/vocabulary-treeview/vocabulary-treeview.service.ts

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@ import {
1010
merge,
1111
mergeMap,
1212
scan,
13+
tap,
1314
} from 'rxjs/operators';
1415

1516
import { PaginatedList } from '../../../core/data/paginated-list.model';
17+
import { RemoteData } from '../../../core/data/remote-data';
1618
import {
19+
getFirstSucceededRemoteData,
1720
getFirstSucceededRemoteDataPayload,
1821
getFirstSucceededRemoteListPayload,
1922
} from '../../../core/shared/operators';
@@ -90,6 +93,12 @@ export class VocabularyTreeviewService {
9093
*/
9194
private hideSearchingWhenUnsubscribed$ = new Observable(() => () => this.loading.next(false));
9295

96+
public currentPage = 1;
97+
public totalPages = 1;
98+
public queryInProgress = '';
99+
public showNextPageSubject = new BehaviorSubject<boolean>(false);
100+
public showPreviousPageSubject = new BehaviorSubject<boolean>(false);
101+
93102
/**
94103
* Initialize instance variables
95104
*
@@ -197,20 +206,51 @@ export class VocabularyTreeviewService {
197206
}
198207

199208
/**
200-
* Perform a search operation by query
209+
* Initiates a vocabulary search using the provided query term and selection, starting from the first page.
210+
*
211+
* @param query - The text input to search for within the vocabulary.
212+
* @param selectedItems - Currently selected vocabulary item IDs to retain in the result.
201213
*/
202214
searchByQuery(query: string, selectedItems: string[]) {
215+
this.searchByQueryAndPage(query, selectedItems, 1);
216+
}
217+
218+
/**
219+
* Executes a paginated vocabulary search with the given query, selection, and page number.
220+
* Updates pagination state, loading indicators, and triggers the vocabulary tree rebuild.
221+
*
222+
* @param query - The search term to filter vocabulary entries.
223+
* @param selectedItems - IDs of items currently selected in the tree.
224+
* @param page - The page number to fetch (1-based index).
225+
*/
226+
searchByQueryAndPage(query: string, selectedItems: string[], page: number = 1) {
203227
this.loading.next(true);
228+
this.queryInProgress = query;
229+
this.currentPage = page;
230+
204231
if (isEmpty(this.storedNodes)) {
205232
this.storedNodes = this.dataChange.value;
206233
this.storedNodeMap = this.nodeMap;
207234
}
208235
this.nodeMap = new Map<string, TreeviewNode>();
209236
this.dataChange.next([]);
210237

211-
this.vocabularyService.getVocabularyEntriesByValue(query, false, this.vocabularyOptions, new PageInfo()).pipe(
238+
const pageInfo = new PageInfo({
239+
elementsPerPage: 20,
240+
currentPage: page,
241+
totalElements: 0,
242+
totalPages: 0,
243+
});
244+
245+
this.vocabularyService.getVocabularyEntriesByValue(query, false, this.vocabularyOptions, pageInfo).pipe(
246+
getFirstSucceededRemoteData(),
247+
tap((rd: RemoteData<PaginatedList<VocabularyEntry>>) => {
248+
this.totalPages = rd.payload.pageInfo.totalPages;
249+
this.showPreviousPageSubject.next(rd.payload.pageInfo.currentPage > 1);
250+
this.showNextPageSubject.next(rd.payload.pageInfo.currentPage < this.totalPages);
251+
}),
212252
getFirstSucceededRemoteListPayload(),
213-
mergeMap((result: VocabularyEntry[]) => (result.length > 0) ? result : of(null)),
253+
mergeMap((result: VocabularyEntry[]) => result.length > 0 ? result : of(null)),
214254
mergeMap((entry: VocabularyEntry) =>
215255
this.vocabularyService.findEntryDetailById(entry.otherInformation.id, this.vocabularyName).pipe(
216256
getFirstSucceededRemoteDataPayload(),

src/assets/i18n/en.json5

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1127,6 +1127,10 @@
11271127

11281128
"browse.title": "Browsing by {{ field }}{{ startsWith }} {{ value }}",
11291129

1130+
"browse.taxonomy.show_next_results": "Show next results",
1131+
1132+
"browse.taxonomy.show_previous_results": "Show previous results",
1133+
11301134
"browse.title.page": "Browsing by {{ field }} {{ value }}",
11311135

11321136
"search.browse.item-back": "Back to Results",

0 commit comments

Comments
 (0)