1
0

add pagination to edit relationships tab

This commit is contained in:
Art Lowel
2021-06-21 16:18:12 +02:00
parent d253790c7d
commit d9a8b8f3fd
3 changed files with 192 additions and 71 deletions

View File

@@ -6,21 +6,30 @@
</button> </button>
</h5> </h5>
<ng-container *ngVar="updates$ | async as updates"> <ng-container *ngVar="updates$ | async as updates">
<ng-container *ngIf="updates"> <ng-container *ngIf="updates && !(loading$ | async)">
<ng-container *ngVar="updates | dsObjectValues as updateValues"> <ng-container *ngVar="updates | dsObjectValues as updateValues">
<ds-edit-relationship *ngFor="let updateValue of updateValues; trackBy: trackUpdate" <ds-pagination
class="relationship-row d-block alert" [paginationOptions]="paginationConfig"
[fieldUpdate]="updateValue || {}" [pageInfoState]="(relationshipsRd$ | async)?.payload?.pageInfo"
[url]="url" [collectionSize]="(relationshipsRd$ | async)?.payload?.totalElements + (this.nbAddedFields$ | async)"
[editItem]="item" [hideGear]="true"
[ngClass]="{ [hidePagerWhenSinglePage]="true">
<div class="my-2">
<ds-edit-relationship *ngFor="let updateValue of updateValues; trackBy: trackUpdate"
class="relationship-row d-block alert"
[fieldUpdate]="updateValue || {}"
[url]="url"
[editItem]="item"
[ngClass]="{
'alert-success': updateValue.changeType === 1, 'alert-success': updateValue.changeType === 1,
'alert-warning': updateValue.changeType === 0, 'alert-warning': updateValue.changeType === 0,
'alert-danger': updateValue.changeType === 2 'alert-danger': updateValue.changeType === 2
}"> }">
</ds-edit-relationship> </ds-edit-relationship>
<div *ngIf="updateValues.length === 0">{{"item.edit.relationships.no-relationships" | translate}}</div> </div>
</ds-pagination>
<div *ngIf="updateValues.length === 0">{{"item.edit.relationships.no-relationships" | translate}}</div>
</ng-container> </ng-container>
</ng-container> </ng-container>
<ds-loading *ngIf="!updates"></ds-loading> <ds-loading *ngIf="loading$ | async"></ds-loading>
</ng-container> </ng-container>

View File

@@ -1,9 +1,14 @@
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit, OnDestroy } from '@angular/core';
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
import { LinkService } from '../../../../core/cache/builders/link.service'; import { LinkService } from '../../../../core/cache/builders/link.service';
import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions'; import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions';
import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service'; import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service';
import { combineLatest as observableCombineLatest, Observable, of } from 'rxjs'; import {
combineLatest as observableCombineLatest,
Observable,
of as observableOf,
from as observableFrom
} from 'rxjs';
import { import {
FieldUpdate, FieldUpdate,
FieldUpdates, FieldUpdates,
@@ -11,14 +16,24 @@ import {
} from '../../../../core/data/object-updates/object-updates.reducer'; } from '../../../../core/data/object-updates/object-updates.reducer';
import { RelationshipService } from '../../../../core/data/relationship.service'; import { RelationshipService } from '../../../../core/data/relationship.service';
import { Item } from '../../../../core/shared/item.model'; import { Item } from '../../../../core/shared/item.model';
import { defaultIfEmpty, map, mergeMap, switchMap, take, startWith } from 'rxjs/operators'; import {
import { hasValue, hasValueOperator } from '../../../../shared/empty.util'; defaultIfEmpty,
map,
mergeMap,
switchMap,
take,
startWith,
toArray,
tap
} from 'rxjs/operators';
import { hasValue, hasValueOperator, hasNoValue } from '../../../../shared/empty.util';
import { Relationship } from '../../../../core/shared/item-relationships/relationship.model'; import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model'; import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model';
import { import {
getAllSucceededRemoteData,
getRemoteDataPayload, getRemoteDataPayload,
getFirstSucceededRemoteData, getFirstSucceededRemoteDataPayload, getFirstSucceededRemoteData,
getFirstSucceededRemoteDataPayload,
getAllSucceededRemoteData,
} from '../../../../core/shared/operators'; } from '../../../../core/shared/operators';
import { ItemType } from '../../../../core/shared/item-relationships/item-type.model'; import { ItemType } from '../../../../core/shared/item-relationships/item-type.model';
import { DsDynamicLookupRelationModalComponent } from '../../../../shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component'; import { DsDynamicLookupRelationModalComponent } from '../../../../shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component';
@@ -30,6 +45,10 @@ import { followLink } from '../../../../shared/utils/follow-link-config.model';
import { PaginatedList } from '../../../../core/data/paginated-list.model'; import { PaginatedList } from '../../../../core/data/paginated-list.model';
import { RemoteData } from '../../../../core/data/remote-data'; import { RemoteData } from '../../../../core/data/remote-data';
import { Collection } from '../../../../core/shared/collection.model'; import { Collection } from '../../../../core/shared/collection.model';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { Subscription } from 'rxjs/internal/Subscription';
import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model';
import { PaginationService } from '../../../../core/pagination/pagination.service';
@Component({ @Component({
selector: 'ds-edit-relationship-list', selector: 'ds-edit-relationship-list',
@@ -40,7 +59,7 @@ import { Collection } from '../../../../core/shared/collection.model';
* A component creating a list of editable relationships of a certain type * A component creating a list of editable relationships of a certain type
* The relationships are rendered as a list of related items * The relationships are rendered as a list of related items
*/ */
export class EditRelationshipListComponent implements OnInit { export class EditRelationshipListComponent implements OnInit, OnDestroy {
/** /**
* The item to display related items for * The item to display related items for
@@ -70,7 +89,38 @@ export class EditRelationshipListComponent implements OnInit {
/** /**
* The FieldUpdates for the relationships in question * The FieldUpdates for the relationships in question
*/ */
updates$: Observable<FieldUpdates>; updates$: BehaviorSubject<FieldUpdates> = new BehaviorSubject(undefined);
/**
* The RemoteData for the relationships
*/
relationshipsRd$: BehaviorSubject<RemoteData<PaginatedList<Relationship>>> = new BehaviorSubject(undefined);
/**
* Whether the current page is the last page
*/
isLastPage$: BehaviorSubject<boolean> = new BehaviorSubject(true);
/**
* Whether we're loading
*/
loading$: BehaviorSubject<boolean> = new BehaviorSubject(true);
/**
* The number of added fields that haven't been saved yet
*/
nbAddedFields$: BehaviorSubject<number> = new BehaviorSubject(0);
/**
* Array to track all subscriptions and unsubscribe them onDestroy
* @type {Array}
*/
private subs: Subscription[] = [];
/**
* The pagination config
*/
paginationConfig: PaginationComponentOptions;
/** /**
* A reference to the lookup window * A reference to the lookup window
@@ -82,6 +132,7 @@ export class EditRelationshipListComponent implements OnInit {
protected linkService: LinkService, protected linkService: LinkService,
protected relationshipService: RelationshipService, protected relationshipService: RelationshipService,
protected modalService: NgbModal, protected modalService: NgbModal,
protected paginationService: PaginationService,
protected selectableListService: SelectableListService, protected selectableListService: SelectableListService,
) { ) {
} }
@@ -172,6 +223,10 @@ export class EditRelationshipListComponent implements OnInit {
this.objectUpdatesService.saveAddFieldUpdate(this.url, update); this.objectUpdatesService.saveAddFieldUpdate(this.url, update);
}); });
} }
this.loading$.next(true);
// emit the last page again to trigger a fieldupdates refresh
this.relationshipsRd$.next(this.relationshipsRd$.getValue());
}); });
}); });
}; };
@@ -186,6 +241,10 @@ export class EditRelationshipListComponent implements OnInit {
) )
); );
}); });
this.loading$.next(true);
// emit the last page again to trigger a fieldupdates refresh
this.relationshipsRd$.next(this.relationshipsRd$.getValue());
}; };
this.relatedEntityType$ this.relatedEntityType$
.pipe(take(1)) .pipe(take(1))
@@ -212,10 +271,10 @@ export class EditRelationshipListComponent implements OnInit {
if (field.relationship) { if (field.relationship) {
return this.getRelatedItem(field.relationship); return this.getRelatedItem(field.relationship);
} else { } else {
return of(field.relatedItem); return observableOf(field.relatedItem);
} }
}) })
) : of([]) ) : observableOf([])
), ),
take(1), take(1),
map((items) => items.map((item) => { map((items) => items.map((item) => {
@@ -286,65 +345,119 @@ export class EditRelationshipListComponent implements OnInit {
(relatedEntityType) => this.listId = `edit-relationship-${this.itemType.id}-${relatedEntityType.id}` (relatedEntityType) => this.listId = `edit-relationship-${this.itemType.id}-${relatedEntityType.id}`
); );
this.updates$ = this.getItemRelationships().pipe( // initialize the pagination options
switchMap((relationships) => this.paginationConfig = new PaginationComponentOptions();
observableCombineLatest( this.paginationConfig.id = `er${this.relationshipType.id}`;
relationships.map((relationship) => this.relationshipService.isLeftItem(relationship, this.item)) this.paginationConfig.pageSize = 5;
).pipe( this.paginationConfig.currentPage = 1;
defaultIfEmpty([]),
map((isLeftItemArray) => isLeftItemArray.map((isLeftItem, index) => { this.subs.push(
const relationship = relationships[index]; // get the pagination params from the route
const nameVariant = isLeftItem ? relationship.rightwardValue : relationship.leftwardValue; this.paginationService.getCurrentPagination(
this.paginationConfig.id,
this.paginationConfig
).pipe(
tap(() => {
this.loading$.next(true);
}),
switchMap((currentPagination: PaginationComponentOptions) =>
// get the relationships for the current item, relationshiptype and page
this.relationshipService.getItemRelationshipsByLabel(
this.item,
this.relationshipType.rightwardType,
{
elementsPerPage: currentPagination.pageSize,
currentPage: currentPagination.currentPage
},
false,
true,
followLink('leftItem'),
followLink('rightItem')
))
).subscribe((rd: RemoteData<PaginatedList<Relationship>>) => {
this.relationshipsRd$.next(rd);
}));
// keep isLastPage$ up to date based on relationshipsRd$
this.subs.push(this.relationshipsRd$.pipe(
getAllSucceededRemoteData()
).subscribe((rd: RemoteData<PaginatedList<Relationship>>) => {
this.isLastPage$.next(hasNoValue(rd.payload._links.next));
}));
this.subs.push(this.relationshipsRd$.pipe(
getAllSucceededRemoteData(),
switchMap((rd: RemoteData<PaginatedList<Relationship>>) =>
// emit each relationship in the page separately
observableFrom(rd.payload.page).pipe(
mergeMap((relationship: Relationship) =>
// check for each relationship whether it's the left item
this.relationshipService.isLeftItem(relationship, this.item).pipe(
// emit an array containing both the relationship and whether it's the left item,
// as we'll need both
map((isLeftItem: boolean) => [relationship, isLeftItem])
)
),
map(([relationship, isLeftItem]: [Relationship, boolean]) => {
// turn it into a RelationshipIdentifiable, an
const nameVariant =
isLeftItem ? relationship.rightwardValue : relationship.leftwardValue;
return { return {
uuid: relationship.id, uuid: relationship.id,
type: this.relationshipType, type: this.relationshipType,
relationship, relationship,
nameVariant, nameVariant,
} as RelationshipIdentifiable; } as RelationshipIdentifiable;
})), }),
)), // wait until all relationships have been processed, and emit them all as a single array
switchMap((initialFields) => this.objectUpdatesService.getFieldUpdates(this.url, initialFields).pipe( toArray(),
map((fieldUpdates) => { // if the pipe above completes without emitting anything, emit an empty array instead
const fieldUpdatesFiltered: FieldUpdates = {}; defaultIfEmpty([])
Object.keys(fieldUpdates).forEach((uuid) => {
if (hasValue(fieldUpdates[uuid])) {
const field = fieldUpdates[uuid].field;
if ((field as RelationshipIdentifiable).type.id === this.relationshipType.id) {
fieldUpdatesFiltered[uuid] = fieldUpdates[uuid];
}
}
});
return fieldUpdatesFiltered;
}),
)), )),
switchMap((nextFields: RelationshipIdentifiable[]) => {
// Get a list that contains the unsaved changes for the page, as well as the page of
// RelationshipIdentifiables, as a single list of FieldUpdates
return this.objectUpdatesService.getFieldUpdates(this.url, nextFields).pipe(
map((fieldUpdates: FieldUpdates) => {
const fieldUpdatesFiltered: FieldUpdates = {};
this.nbAddedFields$.next(0);
// iterate over the fieldupdates and filter out the ones that pertain to this
// relationshiptype
Object.keys(fieldUpdates).forEach((uuid) => {
if (hasValue(fieldUpdates[uuid])) {
const field = fieldUpdates[uuid].field as RelationshipIdentifiable;
// only include fieldupdates regarding this RelationshipType
if (field.type.id === this.relationshipType.id) {
// if it's a newly added relationship
if (fieldUpdates[uuid].changeType === FieldChangeType.ADD) {
// increase the counter that tracks new relationships
this.nbAddedFields$.next(this.nbAddedFields$.getValue() + 1);
if (this.isLastPage$.getValue() === true) {
// only include newly added relationships to the output if we're on the last
// page
fieldUpdatesFiltered[uuid] = fieldUpdates[uuid];
}
} else {
// include all others
fieldUpdatesFiltered[uuid] = fieldUpdates[uuid];
}
}
}
});
return fieldUpdatesFiltered;
}),
);
}),
startWith({}), startWith({}),
); ).subscribe((updates: FieldUpdates) => {
this.loading$.next(false);
this.updates$.next(updates);
}));
} }
private getItemRelationships() { ngOnDestroy(): void {
this.linkService.resolveLink(this.item, this.subs
followLink('relationships', undefined, true, true, true, .filter((subscription) => hasValue(subscription))
followLink('relationshipType'), .forEach((subscription) => subscription.unsubscribe());
followLink('leftItem'),
followLink('rightItem'),
));
return this.item.relationships.pipe(
getAllSucceededRemoteData(),
map((relationships: RemoteData<PaginatedList<Relationship>>) => relationships.payload.page.filter((relationship: Relationship) => hasValue(relationship))),
switchMap((itemRelationships: Relationship[]) =>
observableCombineLatest(
itemRelationships
.map((relationship) => relationship.relationshipType.pipe(
getFirstSucceededRemoteData(),
getRemoteDataPayload(),
))
).pipe(
defaultIfEmpty([]),
map((relationshipTypes) => itemRelationships.filter(
(relationship, index) => relationshipTypes[index].id === this.relationshipType.id)
),
)
),
);
} }
} }

View File

@@ -227,7 +227,6 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent {
* Sends all initial values of this item to the object updates service * Sends all initial values of this item to the object updates service
*/ */
public initializeOriginalFields() { public initializeOriginalFields() {
console.log('init');
return this.relationshipService.getRelatedItems(this.item).pipe( return this.relationshipService.getRelatedItems(this.item).pipe(
take(1), take(1),
).subscribe((items: Item[]) => { ).subscribe((items: Item[]) => {