Skip to content

Commit b3c4912

Browse files
authored
Merge pull request DSpace#4508 from atmire/w2p-132284_fix-issue4500-hierarchical-browse-pagination-10_x
[main] Add paginated vocabulary search with 'Show previous/next results' controls in hierarchical browse
2 parents 3cfc10e + b351e89 commit b3c4912

File tree

4 files changed

+113
-3
lines changed

4 files changed

+113
-3
lines changed

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
@@ -34,6 +34,7 @@ import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap';
3434
import { TranslateModule } from '@ngx-translate/core';
3535
import {
3636
Observable,
37+
of,
3738
Subscription,
3839
} from 'rxjs';
3940
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
*
@@ -269,6 +274,12 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit, OnChanges
269274
* Initialize the component, setting up the data to build the tree
270275
*/
271276
ngOnInit(): void {
277+
278+
// Initialize observables to false when component loads
279+
// Ensures pagination buttons are hidden on first load or after navigation
280+
this.showNextPage$ = of(false);
281+
this.showPreviousPage$ = of(false);
282+
272283
this.subs.push(
273284
this.vocabularyService.findVocabularyById(this.vocabularyOptions.name).pipe(
274285
// Retrieve the configured preloadLevel from REST
@@ -338,6 +349,17 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit, OnChanges
338349
* Search for a vocabulary entry by query
339350
*/
340351
search() {
352+
353+
// Reassign observables after performing each new search
354+
// Updates pagination button visibility based on available pages
355+
this.showNextPage$ = this.vocabularyTreeviewService.showNextPageSubject
356+
? this.vocabularyTreeviewService.showNextPageSubject.asObservable()
357+
: of(false);
358+
359+
this.showPreviousPage$ = this.vocabularyTreeviewService.showPreviousPageSubject
360+
? this.vocabularyTreeviewService.showPreviousPageSubject.asObservable()
361+
: of(false);
362+
341363
if (isNotEmpty(this.searchText)) {
342364
if (isEmpty(this.storedNodeMap)) {
343365
this.storedNodeMap = this.nodeMap;
@@ -347,6 +369,30 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit, OnChanges
347369
}
348370
}
349371

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

378427
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
@@ -1,6 +1,8 @@
11
import { Injectable } from '@angular/core';
22
import { PaginatedList } from '@dspace/core/data/paginated-list.model';
3+
import { RemoteData } from '@dspace/core/data/remote-data';
34
import {
5+
getFirstSucceededRemoteData,
46
getFirstSucceededRemoteDataPayload,
57
getFirstSucceededRemoteListPayload,
68
} from '@dspace/core/shared/operators';
@@ -24,6 +26,7 @@ import {
2426
merge,
2527
mergeMap,
2628
scan,
29+
tap,
2730
} from 'rxjs/operators';
2831

2932
import {
@@ -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
@@ -1177,6 +1177,10 @@
11771177

11781178
"browse.title": "Browsing by {{ field }}{{ startsWith }} {{ value }}",
11791179

1180+
"browse.taxonomy.show_next_results": "Show next results",
1181+
1182+
"browse.taxonomy.show_previous_results": "Show previous results",
1183+
11801184
"browse.title.page": "Browsing by {{ field }} {{ value }}",
11811185

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

0 commit comments

Comments
 (0)