66391: Fixed the valse positive chevron indicating node is expandable

This commit is contained in:
Marie Verdonck
2019-11-19 18:11:03 +01:00
parent 84326ef564
commit eaf0911e2e
3 changed files with 261 additions and 263 deletions

View File

@@ -1,288 +1,288 @@
import { Injectable } from '@angular/core'; import {Injectable} from '@angular/core';
import { combineLatest as observableCombineLatest } from 'rxjs/internal/observable/combineLatest'; import {combineLatest as observableCombineLatest} from 'rxjs/internal/observable/combineLatest';
import { Observable, of as observableOf } from 'rxjs'; import {Observable, of as observableOf} from 'rxjs';
import { CommunityDataService } from '../core/data/community-data.service'; import {CommunityDataService} from '../core/data/community-data.service';
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; import {PaginationComponentOptions} from '../shared/pagination/pagination-component-options.model';
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model'; import {SortDirection, SortOptions} from '../core/cache/models/sort-options.model';
import { catchError, filter, map, switchMap, take, tap } from 'rxjs/operators'; import {catchError, filter, map, switchMap, take} from 'rxjs/operators';
import { Community } from '../core/shared/community.model'; import {Community} from '../core/shared/community.model';
import { Collection } from '../core/shared/collection.model'; import {Collection} from '../core/shared/collection.model';
import { hasValue, isNotEmpty } from '../shared/empty.util'; import {hasValue, isNotEmpty} from '../shared/empty.util';
import { RemoteData } from '../core/data/remote-data'; import {RemoteData} from '../core/data/remote-data';
import { PaginatedList } from '../core/data/paginated-list'; import {PaginatedList} from '../core/data/paginated-list';
import { getCommunityPageRoute } from '../+community-page/community-page-routing.module'; import {getCommunityPageRoute} from '../+community-page/community-page-routing.module';
import { getCollectionPageRoute } from '../+collection-page/collection-page-routing.module'; import {getCollectionPageRoute} from '../+collection-page/collection-page-routing.module';
import { CollectionDataService } from '../core/data/collection-data.service'; import {CollectionDataService} from '../core/data/collection-data.service';
export interface FlatNode { export interface FlatNode {
isExpandable: boolean; isExpandable$: Observable<boolean>;
name: string; name: string;
id: string; id: string;
level: number; level: number;
isExpanded?: boolean; isExpanded?: boolean;
parent?: FlatNode; parent?: FlatNode;
payload: Community | Collection | ShowMoreFlatNode; payload: Community | Collection | ShowMoreFlatNode;
isShowMoreNode: boolean; isShowMoreNode: boolean;
route?: string; route?: string;
currentCommunityPage?: number; currentCommunityPage?: number;
currentCollectionPage?: number; currentCollectionPage?: number;
} }
export class ShowMoreFlatNode { export class ShowMoreFlatNode {
} }
export const combineAndFlatten = (obsList: Array<Observable<FlatNode[]>>): Observable<FlatNode[]> => export const combineAndFlatten = (obsList: Array<Observable<FlatNode[]>>): Observable<FlatNode[]> =>
observableCombineLatest(...obsList).pipe( observableCombineLatest(...obsList).pipe(
map((matrix: FlatNode[][]) => map((matrix: FlatNode[][]) =>
matrix.reduce((combinedList, currentList: FlatNode[]) => [...combinedList, ...currentList])) matrix.reduce((combinedList, currentList: FlatNode[]) => [...combinedList, ...currentList]))
); );
export const toFlatNode = ( export const toFlatNode = (
c: Community | Collection, c: Community | Collection,
isExpandable: boolean, isExpandable: Observable<boolean>,
level: number, level: number,
isExpanded: boolean, isExpanded: boolean,
parent?: FlatNode parent?: FlatNode
): FlatNode => ({ ): FlatNode => ({
isExpandable: isExpandable, isExpandable$: isExpandable,
name: c.name, name: c.name,
id: c.id, id: c.id,
level: level, level: level,
isExpanded, isExpanded,
parent, parent,
payload: c, payload: c,
isShowMoreNode: false, isShowMoreNode: false,
route: c instanceof Community ? getCommunityPageRoute(c.id) : getCollectionPageRoute(c.id), route: c instanceof Community ? getCommunityPageRoute(c.id) : getCollectionPageRoute(c.id),
}); });
export const showMoreFlatNode = ( export const showMoreFlatNode = (
id: string, id: string,
level: number, level: number,
parent: FlatNode parent: FlatNode
): FlatNode => ({ ): FlatNode => ({
isExpandable: false, isExpandable$: observableOf(false),
name: 'Show More Flatnode', name: 'Show More Flatnode',
id: id, id: id,
level: level, level: level,
isExpanded: false, isExpanded: false,
parent: parent, parent: parent,
payload: new ShowMoreFlatNode(), payload: new ShowMoreFlatNode(),
isShowMoreNode: true, isShowMoreNode: true,
}); });
// tslint:disable-next-line:max-classes-per-file // tslint:disable-next-line:max-classes-per-file
@Injectable() @Injectable()
export class CommunityListService { export class CommunityListService {
// page-limited list of top-level communities // page-limited list of top-level communities
payloads$: Array<Observable<PaginatedList<Community>>>; payloads$: Array<Observable<PaginatedList<Community>>>;
topCommunitiesConfig: PaginationComponentOptions; topCommunitiesConfig: PaginationComponentOptions;
topCommunitiesSortConfig: SortOptions; topCommunitiesSortConfig: SortOptions;
maxSubCommunitiesPerPage: number; maxSubCommunitiesPerPage: number;
maxCollectionsPerPage: number; maxCollectionsPerPage: number;
constructor(private communityDataService: CommunityDataService, private collectionDataService: CollectionDataService) { constructor(private communityDataService: CommunityDataService, private collectionDataService: CollectionDataService) {
this.topCommunitiesConfig = new PaginationComponentOptions(); this.topCommunitiesConfig = new PaginationComponentOptions();
this.topCommunitiesConfig.id = 'top-level-pagination'; this.topCommunitiesConfig.id = 'top-level-pagination';
this.topCommunitiesConfig.pageSize = 10; this.topCommunitiesConfig.pageSize = 10;
this.topCommunitiesConfig.currentPage = 1; this.topCommunitiesConfig.currentPage = 1;
this.topCommunitiesSortConfig = new SortOptions('dc.title', SortDirection.ASC); this.topCommunitiesSortConfig = new SortOptions('dc.title', SortDirection.ASC);
this.initTopCommunityList(); this.initTopCommunityList();
this.maxSubCommunitiesPerPage = 3; this.maxSubCommunitiesPerPage = 3;
this.maxCollectionsPerPage = 3; this.maxCollectionsPerPage = 3;
} }
/** /**
* Increases the payload so it contains the next page of top level communities * Increases the payload so it contains the next page of top level communities
*/ */
getNextPageTopCommunities(): void { getNextPageTopCommunities(): void {
this.topCommunitiesConfig.currentPage = this.topCommunitiesConfig.currentPage + 1; this.topCommunitiesConfig.currentPage = this.topCommunitiesConfig.currentPage + 1;
this.payloads$ = [...this.payloads$, this.communityDataService.findTop({ this.payloads$ = [...this.payloads$, this.communityDataService.findTop({
currentPage: this.topCommunitiesConfig.currentPage, currentPage: this.topCommunitiesConfig.currentPage,
elementsPerPage: this.topCommunitiesConfig.pageSize, elementsPerPage: this.topCommunitiesConfig.pageSize,
sort: { sort: {
field: this.topCommunitiesSortConfig.field, field: this.topCommunitiesSortConfig.field,
direction: this.topCommunitiesSortConfig.direction direction: this.topCommunitiesSortConfig.direction
} }
}).pipe( }).pipe(
take(1), take(1),
map((results) => results.payload), map((results) => results.payload),
)]; )];
} }
/** /**
* Gets all top communities, limited by page, and transforms this in a list of flatnodes. * Gets all top communities, limited by page, and transforms this in a list of flatnodes.
* @param expandedNodes List of expanded nodes; if a node is not expanded its subcommunities and collections need not be added to the list * @param expandedNodes List of expanded nodes; if a node is not expanded its subcommunities and collections need not be added to the list
*/ */
loadCommunities(expandedNodes: FlatNode[]): Observable<FlatNode[]> { loadCommunities(expandedNodes: FlatNode[]): Observable<FlatNode[]> {
const res = this.payloads$.map((payload) => { const res = this.payloads$.map((payload) => {
return payload.pipe( return payload.pipe(
take(1), take(1),
switchMap((result: PaginatedList<Community>) => { switchMap((result: PaginatedList<Community>) => {
return this.transformListOfCommunities(result, 0, null, expandedNodes); return this.transformListOfCommunities(result, 0, null, expandedNodes);
}), }),
catchError(() => observableOf([])), catchError(() => observableOf([])),
); );
});
return combineAndFlatten(res);
};
/**
* Puts the initial top level communities in a list to be called upon
*/
private initTopCommunityList(): void {
this.payloads$ = [this.communityDataService.findTop({
currentPage: this.topCommunitiesConfig.currentPage,
elementsPerPage: this.topCommunitiesConfig.pageSize,
sort: {
field: this.topCommunitiesSortConfig.field,
direction: this.topCommunitiesSortConfig.direction
}
}).pipe(
take(1),
map((results) => results.payload),
)];
}
/**
* Transforms a list of communities to a list of FlatNodes according to the instructions detailed in transformCommunity
* @param listOfPaginatedCommunities Paginated list of communities to be transformed
* @param level Level the tree is currently at
* @param parent FlatNode of the parent of this list of communities
* @param expandedNodes List of expanded nodes; if a node is not expanded its subcommunities and collections need not be added to the list
*/
public transformListOfCommunities(listOfPaginatedCommunities: PaginatedList<Community>,
level: number,
parent: FlatNode,
expandedNodes: FlatNode[]): Observable<FlatNode[]> {
if (isNotEmpty(listOfPaginatedCommunities.page)) {
let currentPage = this.topCommunitiesConfig.currentPage;
if (isNotEmpty(parent)) {
currentPage = expandedNodes.find((node: FlatNode) => node.id === parent.id).currentCommunityPage;
}
const isNotAllCommunities = (listOfPaginatedCommunities.totalElements > (listOfPaginatedCommunities.elementsPerPage * currentPage));
let obsList = listOfPaginatedCommunities.page
.map((community: Community) => {
return this.transformCommunity(community, level, parent, expandedNodes)
}); });
if (isNotAllCommunities && listOfPaginatedCommunities.currentPage > currentPage) { return combineAndFlatten(res);
obsList = [...obsList, observableOf([showMoreFlatNode('community', level, parent)])]; };
}
return combineAndFlatten(obsList); /**
} else { * Puts the initial top level communities in a list to be called upon
return observableOf([]); */
} private initTopCommunityList(): void {
} this.payloads$ = [this.communityDataService.findTop({
currentPage: this.topCommunitiesConfig.currentPage,
/** elementsPerPage: this.topCommunitiesConfig.pageSize,
* Transforms a community in a list of FlatNodes containing firstly a flatnode of the community itself, sort: {
* followed by flatNodes of its possible subcommunities and collection field: this.topCommunitiesSortConfig.field,
* It gets called recursively for each subcommunity to add its subcommunities and collections to the list direction: this.topCommunitiesSortConfig.direction
* Number of subcommunities and collections added, is dependant on the current page the parent is at for respectively subcommunities and collections. }
* @param community Community being transformed }).pipe(
* @param level Depth of the community in the list, subcommunities and collections go one level deeper
* @param parent Flatnode of the parent community
* @param expandedNodes List of nodes which are expanded, if node is not expanded, it need not add its page-limited subcommunities or collections
*/
public transformCommunity(community: Community, level: number, parent: FlatNode, expandedNodes: FlatNode[]): Observable<FlatNode[]> {
let isExpanded = false;
if (isNotEmpty(expandedNodes)) {
isExpanded = hasValue(expandedNodes.find((node) => (node.id === community.id)));
}
const isExpandable = this.getIsExpandable(community);
const communityFlatNode = toFlatNode(community, isExpandable, level, isExpanded, parent);
let obsList = [observableOf([communityFlatNode])];
if (isExpanded) {
const currentCommunityPage = expandedNodes.find((node: FlatNode) => node.id === community.id).currentCommunityPage;
let subcoms = [];
for (let i = 1; i <= currentCommunityPage; i++) {
const nextSetOfSubcommunitiesPage = this.communityDataService.findByParent(community.uuid, {
elementsPerPage: this.maxSubCommunitiesPerPage,
currentPage: i
})
.pipe(
filter((rd: RemoteData<PaginatedList<Community>>) => rd.hasSucceeded),
take(1), take(1),
switchMap((rd: RemoteData<PaginatedList<Community>>) => map((results) => results.payload),
this.transformListOfCommunities(rd.payload, level + 1, communityFlatNode, expandedNodes)) )];
); }
subcoms = [...subcoms, nextSetOfSubcommunitiesPage]; /**
} * Transforms a list of communities to a list of FlatNodes according to the instructions detailed in transformCommunity
* @param listOfPaginatedCommunities Paginated list of communities to be transformed
* @param level Level the tree is currently at
* @param parent FlatNode of the parent of this list of communities
* @param expandedNodes List of expanded nodes; if a node is not expanded its subcommunities and collections need not be added to the list
*/
public transformListOfCommunities(listOfPaginatedCommunities: PaginatedList<Community>,
level: number,
parent: FlatNode,
expandedNodes: FlatNode[]): Observable<FlatNode[]> {
if (isNotEmpty(listOfPaginatedCommunities.page)) {
let currentPage = this.topCommunitiesConfig.currentPage;
if (isNotEmpty(parent)) {
currentPage = expandedNodes.find((node: FlatNode) => node.id === parent.id).currentCommunityPage;
}
const isNotAllCommunities = (listOfPaginatedCommunities.totalElements > (listOfPaginatedCommunities.elementsPerPage * currentPage));
let obsList = listOfPaginatedCommunities.page
.map((community: Community) => {
return this.transformCommunity(community, level, parent, expandedNodes)
});
if (isNotAllCommunities && listOfPaginatedCommunities.currentPage > currentPage) {
obsList = [...obsList, observableOf([showMoreFlatNode('community', level, parent)])];
}
obsList = [...obsList, combineAndFlatten(subcoms)]; return combineAndFlatten(obsList);
} else {
return observableOf([]);
}
}
const currentCollectionPage = expandedNodes.find((node: FlatNode) => node.id === community.id).currentCollectionPage; /**
let collections = []; * Transforms a community in a list of FlatNodes containing firstly a flatnode of the community itself,
for (let i = 1; i <= currentCollectionPage; i++) { * followed by flatNodes of its possible subcommunities and collection
const nextSetOfCollectionsPage = this.collectionDataService.findByParent(community.uuid, { * It gets called recursively for each subcommunity to add its subcommunities and collections to the list
elementsPerPage: this.maxCollectionsPerPage, * Number of subcommunities and collections added, is dependant on the current page the parent is at for respectively subcommunities and collections.
currentPage: i * @param community Community being transformed
}) * @param level Depth of the community in the list, subcommunities and collections go one level deeper
.pipe( * @param parent Flatnode of the parent community
filter((rd: RemoteData<PaginatedList<Collection>>) => rd.hasSucceeded), * @param expandedNodes List of nodes which are expanded, if node is not expanded, it need not add its page-limited subcommunities or collections
*/
public transformCommunity(community: Community, level: number, parent: FlatNode, expandedNodes: FlatNode[]): Observable<FlatNode[]> {
let isExpanded = false;
if (isNotEmpty(expandedNodes)) {
isExpanded = hasValue(expandedNodes.find((node) => (node.id === community.id)));
}
const isExpandable$ = this.getIsExpandable(community);
const communityFlatNode = toFlatNode(community, isExpandable$, level, isExpanded, parent);
let obsList = [observableOf([communityFlatNode])];
if (isExpanded) {
const currentCommunityPage = expandedNodes.find((node: FlatNode) => node.id === community.id).currentCommunityPage;
let subcoms = [];
for (let i = 1; i <= currentCommunityPage; i++) {
const nextSetOfSubcommunitiesPage = this.communityDataService.findByParent(community.uuid, {
elementsPerPage: this.maxSubCommunitiesPerPage,
currentPage: i
})
.pipe(
filter((rd: RemoteData<PaginatedList<Community>>) => rd.hasSucceeded),
take(1),
switchMap((rd: RemoteData<PaginatedList<Community>>) =>
this.transformListOfCommunities(rd.payload, level + 1, communityFlatNode, expandedNodes))
);
subcoms = [...subcoms, nextSetOfSubcommunitiesPage];
}
obsList = [...obsList, combineAndFlatten(subcoms)];
const currentCollectionPage = expandedNodes.find((node: FlatNode) => node.id === community.id).currentCollectionPage;
let collections = [];
for (let i = 1; i <= currentCollectionPage; i++) {
const nextSetOfCollectionsPage = this.collectionDataService.findByParent(community.uuid, {
elementsPerPage: this.maxCollectionsPerPage,
currentPage: i
})
.pipe(
filter((rd: RemoteData<PaginatedList<Collection>>) => rd.hasSucceeded),
take(1),
map((rd: RemoteData<PaginatedList<Collection>>) => {
let nodes = rd.payload.page
.map((collection: Collection) => toFlatNode(collection, observableOf(false), level + 1, false, communityFlatNode));
if ((rd.payload.elementsPerPage * currentCollectionPage) < rd.payload.totalElements && rd.payload.currentPage > currentCollectionPage) {
nodes = [...nodes, showMoreFlatNode('collection', level + 1, communityFlatNode)];
}
return nodes;
}),
);
collections = [...collections, nextSetOfCollectionsPage];
}
obsList = [...obsList, combineAndFlatten(collections)];
}
return combineAndFlatten(obsList);
}
/**
* Checks if a community has subcommunities or collections by querying the respective services with a pageSize = 0
* Returns an observable that combines the result.payload.totalElements fo the observables that the
* respective services return when queried
* @param community Community being checked whether it is expandable (if it has subcommunities or collections)
*/
public getIsExpandable(community: Community): Observable<boolean> {
let hasSubcoms$: Observable<boolean>;
let hasColls$: Observable<boolean>;
hasSubcoms$ = this.communityDataService.findByParent(community.uuid, {elementsPerPage: 1})
.pipe(
filter((rd: RemoteData<PaginatedList<Community>>) => rd.hasSucceeded),
take(1),
map((results) => results.payload.totalElements > 0),
);
hasColls$ = this.collectionDataService.findByParent(community.uuid, {elementsPerPage: 1})
.pipe(
filter((rd: RemoteData<PaginatedList<Community>>) => rd.hasSucceeded),
take(1),
map((results) => results.payload.totalElements > 0),
);
let hasChildren$: Observable<boolean>;
hasChildren$ = observableCombineLatest(hasSubcoms$, hasColls$).pipe(
take(1), take(1),
map((rd: RemoteData<PaginatedList<Collection>>) => { map((result: [boolean]) => {
let nodes = rd.payload.page if (result[0] || result[1]) {
.map((collection: Collection) => toFlatNode(collection, false, level + 1, false, communityFlatNode)); return true;
if ((rd.payload.elementsPerPage * currentCollectionPage) < rd.payload.totalElements && rd.payload.currentPage > currentCollectionPage) { } else {
nodes = [...nodes, showMoreFlatNode('collection', level + 1, communityFlatNode)]; return false;
} }
return nodes; })
}), );
);
collections = [...collections, nextSetOfCollectionsPage]; return hasChildren$;
}
obsList = [...obsList, combineAndFlatten(collections)];
} }
return combineAndFlatten(obsList);
}
/**
* Checks if a community has subcommunities or collections by querying the respective services with a pageSize = 0
* If it does, it is expandable => returns true, false if not
* @param community Community being checked whether it is expandable (if it has subcommunities or collections)
*/
public getIsExpandable(community: Community): boolean {
let isExpandable = true;
let nrOfSubcoms;
let nrOfColl;
this.communityDataService.findByParent(community.uuid, { elementsPerPage: 0 })
.pipe(
filter((rd: RemoteData<PaginatedList<Community>>) => rd.hasSucceeded),
take(1),
)
.subscribe((result) => {
if (result.payload.totalElements === 0) {
nrOfSubcoms = result.payload.totalElements;
}
;
});
this.collectionDataService.findByParent(community.uuid, { elementsPerPage: 0 })
.pipe(
filter((rd: RemoteData<PaginatedList<Collection>>) => rd.hasSucceeded),
take(1),
)
.subscribe((result) => {
if (result.payload.totalElements === 0) {
nrOfColl = result.payload.totalElements;
}
;
});
if (nrOfSubcoms === 0 && nrOfColl === 0) {
isExpandable = false;
}
return isExpandable;
}
} }

View File

@@ -28,7 +28,7 @@
<button type="button" class="btn btn-default" cdkTreeNodeToggle <button type="button" class="btn btn-default" cdkTreeNodeToggle
[attr.aria-label]="'toggle ' + node.name" [attr.aria-label]="'toggle ' + node.name"
(click)="toggleExpanded(node)" (click)="toggleExpanded(node)"
[ngClass]="node.isExpandable ? 'visible' : 'invisible'"> [ngClass]="(node.isExpandable$ | async) ? 'visible' : 'invisible'">
<span class="{{node.isExpanded ? 'fa fa-chevron-down' : 'fa fa-chevron-right'}}" <span class="{{node.isExpanded ? 'fa fa-chevron-down' : 'fa fa-chevron-right'}}"
aria-hidden="true"></span> aria-hidden="true"></span>
</button> </button>

View File

@@ -14,7 +14,7 @@ export class CommunityListComponent implements OnInit {
public loadingNode: FlatNode; public loadingNode: FlatNode;
treeControl = new FlatTreeControl<FlatNode>( treeControl = new FlatTreeControl<FlatNode>(
(node) => node.level, (node) => node.isExpandable (node) => node.level, (node) => true
); );
dataSource: CommunityListDatasource; dataSource: CommunityListDatasource;
@@ -29,7 +29,7 @@ export class CommunityListComponent implements OnInit {
// whether or not this node has children (subcommunities or collections) // whether or not this node has children (subcommunities or collections)
hasChild(_: number, node: FlatNode) { hasChild(_: number, node: FlatNode) {
return node.isExpandable; return node.isExpandable$;
} }
// whether or not it is a show more node (contains no data, but is indication that there are more topcoms, subcoms or collections // whether or not it is a show more node (contains no data, but is indication that there are more topcoms, subcoms or collections
@@ -68,15 +68,13 @@ export class CommunityListComponent implements OnInit {
getNextPage(node: FlatNode): void { getNextPage(node: FlatNode): void {
this.loadingNode = node; this.loadingNode = node;
if (node.parent != null) { if (node.parent != null) {
if (node.parent.isExpandable) { if (node.id === 'collection') {
if (node.id === 'collection') { const parentNodeInExpandedNodes = this.expandedNodes.find((node2: FlatNode) => node.parent.id === node2.id);
const parentNodeInExpandedNodes = this.expandedNodes.find((node2: FlatNode) => node.parent.id === node2.id); parentNodeInExpandedNodes.currentCollectionPage++;
parentNodeInExpandedNodes.currentCollectionPage++; }
} if (node.id === 'community') {
if (node.id === 'community') { const parentNodeInExpandedNodes = this.expandedNodes.find((node2: FlatNode) => node.parent.id === node2.id);
const parentNodeInExpandedNodes = this.expandedNodes.find((node2: FlatNode) => node.parent.id === node2.id); parentNodeInExpandedNodes.currentCommunityPage++;
parentNodeInExpandedNodes.currentCommunityPage++;
}
} }
this.dataSource.loadCommunities(this.expandedNodes); this.dataSource.loadCommunities(this.expandedNodes);
} else { } else {