turned filterValues in to a RemoteData object, added fade animations

This commit is contained in:
Art Lowel
2018-07-24 16:55:11 +02:00
parent 367e832e62
commit 1906f07be3
10 changed files with 104 additions and 53 deletions

View File

@@ -6,8 +6,9 @@
<input type="checkbox" [checked]="true" class="my-1 align-self-stretch"/> <input type="checkbox" [checked]="true" class="my-1 align-self-stretch"/>
<span class="filter-value pl-1">{{value}}</span> <span class="filter-value pl-1">{{value}}</span>
</a> </a>
<ng-container *ngFor="let page of (filterValues$ | async)"> <ng-container *ngFor="let page of (filterValues$ | async)?.payload">
<ng-container *ngFor="let value of (page | async)?.payload.page; let i=index"> <div [@facetLoad]="animationState">
<ng-container *ngFor="let value of page.page; let i=index">
<a *ngIf="!(selectedValues | async).includes(value.value)" class="d-flex flex-row" <a *ngIf="!(selectedValues | async).includes(value.value)" class="d-flex flex-row"
[routerLink]="[getSearchLink()]" [routerLink]="[getSearchLink()]"
[queryParams]="getAddParams(value.value) | async" queryParamsHandling="merge"> [queryParams]="getAddParams(value.value) | async" queryParamsHandling="merge">
@@ -18,6 +19,7 @@
</span> </span>
</a> </a>
</ng-container> </ng-container>
</div>
</ng-container> </ng-container>
<div class="clearfix toggle-more-filters"> <div class="clearfix toggle-more-filters">
<a class="float-left" *ngIf="!(isLastPage$ | async)" <a class="float-left" *ngIf="!(isLastPage$ | async)"

View File

@@ -2,7 +2,10 @@ import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { FilterType } from '../../../search-service/filter-type.model'; import { FilterType } from '../../../search-service/filter-type.model';
import { renderFacetFor } from '../search-filter-type-decorator'; import { renderFacetFor } from '../search-filter-type-decorator';
import { SearchFacetFilterComponent } from '../search-facet-filter/search-facet-filter.component'; import {
facetLoad,
SearchFacetFilterComponent
} from '../search-facet-filter/search-facet-filter.component';
/** /**
* This component renders a simple item page. * This component renders a simple item page.
@@ -14,6 +17,7 @@ import { SearchFacetFilterComponent } from '../search-facet-filter/search-facet-
selector: 'ds-search-boolean-filter', selector: 'ds-search-boolean-filter',
styleUrls: ['./search-boolean-filter.component.scss'], styleUrls: ['./search-boolean-filter.component.scss'],
templateUrl: './search-boolean-filter.component.html', templateUrl: './search-boolean-filter.component.html',
animations: [facetLoad]
}) })
@renderFacetFor(FilterType.boolean) @renderFacetFor(FilterType.boolean)

View File

@@ -1,17 +1,20 @@
import { Component, Inject, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core'; import { animate, state, style, transition, trigger } from '@angular/animations';
import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import { Subscription } from 'rxjs/Subscription';
import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service';
import { PaginatedList } from '../../../../core/data/paginated-list';
import { RemoteData } from '../../../../core/data/remote-data';
import { hasNoValue, hasValue, isNotEmpty } from '../../../../shared/empty.util';
import { EmphasizePipe } from '../../../../shared/utils/emphasize.pipe';
import { SearchOptions } from '../../../search-options.model';
import { FacetValue } from '../../../search-service/facet-value.model'; import { FacetValue } from '../../../search-service/facet-value.model';
import { SearchFilterConfig } from '../../../search-service/search-filter-config.model'; import { SearchFilterConfig } from '../../../search-service/search-filter-config.model';
import { Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { FILTER_CONFIG, SearchFilterService } from '../search-filter.service';
import { hasNoValue, hasValue, isNotEmpty } from '../../../../shared/empty.util';
import { RemoteData } from '../../../../core/data/remote-data';
import { PaginatedList } from '../../../../core/data/paginated-list';
import { SearchService } from '../../../search-service/search.service'; import { SearchService } from '../../../search-service/search.service';
import { SearchOptions } from '../../../search-options.model'; import { FILTER_CONFIG, SearchFilterService } from '../search-filter.service';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Subscription } from 'rxjs/Subscription';
import { EmphasizePipe } from '../../../../shared/utils/emphasize.pipe';
/** /**
* This component renders a simple item page. * This component renders a simple item page.
@@ -26,48 +29,66 @@ import { EmphasizePipe } from '../../../../shared/utils/emphasize.pipe';
export class SearchFacetFilterComponent implements OnInit, OnDestroy { export class SearchFacetFilterComponent implements OnInit, OnDestroy {
filterValues: Array<Observable<RemoteData<PaginatedList<FacetValue>>>> = []; filterValues: Array<Observable<RemoteData<PaginatedList<FacetValue>>>> = [];
filterValues$: BehaviorSubject<any> = new BehaviorSubject(this.filterValues); filterValues$: Subject<RemoteData<Array<PaginatedList<FacetValue>>>>;
currentPage: Observable<number>; currentPage: Observable<number>;
isLastPage$: BehaviorSubject<boolean> = new BehaviorSubject(false); isLastPage$: BehaviorSubject<boolean> = new BehaviorSubject(false);
filter: string; filter: string;
pageChange = false; pageChange = false;
sub: Subscription; private subs: Subscription[] = [];
filterSearchResults: Observable<any[]> = Observable.of([]); filterSearchResults: Observable<any[]> = Observable.of([]);
selectedValues: Observable<string[]>; selectedValues: Observable<string[]>;
private collapseNextUpdate = true;
animationState = 'loading';
constructor(protected searchService: SearchService, constructor(protected searchService: SearchService,
protected filterService: SearchFilterService, protected filterService: SearchFilterService,
protected rdbs: RemoteDataBuildService,
protected router: Router, protected router: Router,
@Inject(FILTER_CONFIG) public filterConfig: SearchFilterConfig) { @Inject(FILTER_CONFIG) public filterConfig: SearchFilterConfig) {
} }
ngOnInit(): void { ngOnInit(): void {
this.filterValues$ = new BehaviorSubject(new RemoteData(true, false, undefined, undefined, undefined));
this.currentPage = this.getCurrentPage().distinctUntilChanged(); this.currentPage = this.getCurrentPage().distinctUntilChanged();
this.selectedValues = this.filterService.getSelectedValuesForFilter(this.filterConfig); this.selectedValues = this.filterService.getSelectedValuesForFilter(this.filterConfig);
const searchOptions = this.filterService.getSearchOptions().distinctUntilChanged(); const searchOptions = this.filterService.getSearchOptions().distinctUntilChanged();
searchOptions.subscribe((options) => this.updateFilterValueList(options)); this.subs.push(searchOptions.subscribe((options) => this.updateFilterValueList(options)));
const facetValues = Observable.combineLatest(searchOptions, this.currentPage, (options, page) => { const facetValues = Observable.combineLatest(searchOptions, this.currentPage, (options, page) => {
return {values: this.searchService.getFacetValuesFor(this.filterConfig, page, options), page: page}; return {values: this.searchService.getFacetValuesFor(this.filterConfig, page, options), page: page};
}); });
facetValues.subscribe((facetOutcome) => {
this.subs.push(facetValues.subscribe((facetOutcome) => {
const newValues$ = facetOutcome.values; const newValues$ = facetOutcome.values;
if (facetOutcome.page > 1) { if (this.collapseNextUpdate) {
this.filterValues = [...this.filterValues, newValues$]; this.showFirstPageOnly();
} else { facetOutcome.page = 1;
this.filterValues = [newValues$] this.collapseNextUpdate = false;
}
if (facetOutcome.page === 1) {
this.filterValues = [];
} }
this.filterValues$.next(this.filterValues); this.filterValues = [...this.filterValues, newValues$];
newValues$.first().subscribe((rd) => {
this.subs.push(this.rdbs.aggregate(this.filterValues).subscribe((rd: RemoteData<Array<PaginatedList<FacetValue>>>) => {
this.animationState = 'ready';
this.filterValues$.next(rd);
}));
this.subs.push(newValues$.first().subscribe((rd) => {
this.isLastPage$.next(hasNoValue(rd.payload.next)) this.isLastPage$.next(hasNoValue(rd.payload.next))
}); }));
}); }));
} }
updateFilterValueList(options: SearchOptions) { updateFilterValueList(options: SearchOptions) {
// this.unsubscribe(); // this.showFirstPageOnly();
this.showFirstPageOnly(); this.animationState = 'loading';
this.collapseNextUpdate = true;
this.filter = ''; this.filter = '';
} }
@@ -138,13 +159,9 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
} }
ngOnDestroy(): void { ngOnDestroy(): void {
this.unsubscribe(); this.subs
} .filter((sub) => hasValue(sub))
.forEach((sub) => sub.unsubscribe());
unsubscribe(): void {
if (hasValue(this.sub)) {
this.sub.unsubscribe();
}
} }
findSuggestions(data): void { findSuggestions(data): void {
@@ -171,3 +188,9 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
return new EmphasizePipe().transform(facet.value, query) + ' (' + facet.count + ')'; return new EmphasizePipe().transform(facet.value, query) + ' (' + facet.count + ')';
} }
} }
export const facetLoad = trigger('facetLoad', [
state('ready', style({ opacity: 1 })),
state('loading', style({ opacity: 0 })),
transition('loading <=> ready', animate(100)),
]);

View File

@@ -6,8 +6,9 @@
<input type="checkbox" [checked]="true" class="my-1 align-self-stretch"/> <input type="checkbox" [checked]="true" class="my-1 align-self-stretch"/>
<span class="filter-value pl-1">{{value}}</span> <span class="filter-value pl-1">{{value}}</span>
</a> </a>
<ng-container *ngFor="let page of (filterValues$ | async)"> <ng-container *ngFor="let page of (filterValues$ | async)?.payload">
<ng-container *ngFor="let value of (page | async)?.payload.page; let i=index"> <div [@facetLoad]="animationState">
<ng-container *ngFor="let value of page.page; let i=index">
<a *ngIf="!(selectedValues | async).includes(value.value)" class="d-flex flex-row" <a *ngIf="!(selectedValues | async).includes(value.value)" class="d-flex flex-row"
[routerLink]="[getSearchLink()]" [routerLink]="[getSearchLink()]"
[queryParams]="getAddParams(value.value) | async" queryParamsHandling="merge" > [queryParams]="getAddParams(value.value) | async" queryParamsHandling="merge" >
@@ -18,6 +19,7 @@
</span> </span>
</a> </a>
</ng-container> </ng-container>
</div>
</ng-container> </ng-container>
<div class="clearfix toggle-more-filters"> <div class="clearfix toggle-more-filters">
<a class="float-left" *ngIf="!(isLastPage$ | async)" <a class="float-left" *ngIf="!(isLastPage$ | async)"

View File

@@ -3,7 +3,10 @@ import { FacetValue } from '../../../search-service/facet-value.model';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { FilterType } from '../../../search-service/filter-type.model'; import { FilterType } from '../../../search-service/filter-type.model';
import { renderFacetFor } from '../search-filter-type-decorator'; import { renderFacetFor } from '../search-filter-type-decorator';
import { SearchFacetFilterComponent } from '../search-facet-filter/search-facet-filter.component'; import {
facetLoad,
SearchFacetFilterComponent
} from '../search-facet-filter/search-facet-filter.component';
/** /**
* This component renders a simple item page. * This component renders a simple item page.
@@ -15,6 +18,7 @@ import { SearchFacetFilterComponent } from '../search-facet-filter/search-facet-
selector: 'ds-search-hierarchy-filter', selector: 'ds-search-hierarchy-filter',
styleUrls: ['./search-hierarchy-filter.component.scss'], styleUrls: ['./search-hierarchy-filter.component.scss'],
templateUrl: './search-hierarchy-filter.component.html', templateUrl: './search-hierarchy-filter.component.html',
animations: [facetLoad]
}) })
@renderFacetFor(FilterType.hierarchy) @renderFacetFor(FilterType.hierarchy)

View File

@@ -23,8 +23,9 @@
ngDefaultControl></nouislider> ngDefaultControl></nouislider>
</ng-container> </ng-container>
<ng-container *ngFor="let page of (filterValues$ | async)"> <ng-container *ngFor="let page of (filterValues$ | async)?.payload">
<ng-container *ngFor="let value of (page | async)?.payload.page; let i=index"> <div [@facetLoad]="animationState">
<ng-container *ngFor="let value of page.page; let i=index">
<a *ngIf="!(selectedValues | async).includes(value.value)" class="d-flex flex-row" <a *ngIf="!(selectedValues | async).includes(value.value)" class="d-flex flex-row"
[routerLink]="[getSearchLink()]" [routerLink]="[getSearchLink()]"
[queryParams]="getAddParams(value.value) | async" queryParamsHandling="merge"> [queryParams]="getAddParams(value.value) | async" queryParamsHandling="merge">
@@ -34,6 +35,7 @@
</span> </span>
</a> </a>
</ng-container> </ng-container>
</div>
</ng-container> </ng-container>
</div> </div>
</div> </div>

View File

@@ -1,8 +1,12 @@
import { isPlatformBrowser } from '@angular/common'; import { isPlatformBrowser } from '@angular/common';
import { Component, Inject, OnInit, PLATFORM_ID } from '@angular/core'; import { Component, Inject, OnInit, PLATFORM_ID } from '@angular/core';
import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service';
import { FilterType } from '../../../search-service/filter-type.model'; import { FilterType } from '../../../search-service/filter-type.model';
import { renderFacetFor } from '../search-filter-type-decorator'; import { renderFacetFor } from '../search-filter-type-decorator';
import { SearchFacetFilterComponent } from '../search-facet-filter/search-facet-filter.component'; import {
facetLoad,
SearchFacetFilterComponent
} from '../search-facet-filter/search-facet-filter.component';
import { SearchFilterConfig } from '../../../search-service/search-filter-config.model'; import { SearchFilterConfig } from '../../../search-service/search-filter-config.model';
import { FILTER_CONFIG, SearchFilterService } from '../search-filter.service'; import { FILTER_CONFIG, SearchFilterService } from '../search-filter.service';
import { SearchService } from '../../../search-service/search.service'; import { SearchService } from '../../../search-service/search.service';
@@ -24,6 +28,7 @@ const rangeDelimiter = '-';
selector: 'ds-search-range-filter', selector: 'ds-search-range-filter',
styleUrls: ['./search-range-filter.component.scss'], styleUrls: ['./search-range-filter.component.scss'],
templateUrl: './search-range-filter.component.html', templateUrl: './search-range-filter.component.html',
animations: [facetLoad]
}) })
@renderFacetFor(FilterType.range) @renderFacetFor(FilterType.range)
@@ -35,10 +40,11 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple
constructor(protected searchService: SearchService, constructor(protected searchService: SearchService,
protected filterService: SearchFilterService, protected filterService: SearchFilterService,
protected router: Router, protected router: Router,
protected rdbs: RemoteDataBuildService,
@Inject(FILTER_CONFIG) public filterConfig: SearchFilterConfig, @Inject(FILTER_CONFIG) public filterConfig: SearchFilterConfig,
@Inject(PLATFORM_ID) private platformId: any, @Inject(PLATFORM_ID) private platformId: any,
private route: ActivatedRoute) { private route: ActivatedRoute) {
super(searchService, filterService, router, filterConfig); super(searchService, filterService, rdbs, router, filterConfig);
} }
ngOnInit(): void { ngOnInit(): void {

View File

@@ -6,18 +6,22 @@
<input type="checkbox" [checked]="true" class="my-1 align-self-stretch"/> <input type="checkbox" [checked]="true" class="my-1 align-self-stretch"/>
<span class="filter-value pl-1">{{value}}</span> <span class="filter-value pl-1">{{value}}</span>
</a> </a>
<ng-container *ngFor="let page of (filterValues$ | async)"> <ng-container *ngVar="(filterValues$ | async) as filterValuesRD">
<ng-container *ngFor="let value of (page | async)?.payload.page; let i=index"> <div [@facetLoad]="animationState">
<ng-container *ngFor="let page of filterValuesRD?.payload">
<ng-container *ngFor="let value of page.page; let i=index">
<a *ngIf="!(selectedValues | async).includes(value.value)" class="d-flex flex-row" <a *ngIf="!(selectedValues | async).includes(value.value)" class="d-flex flex-row"
[routerLink]="[getSearchLink()]" [routerLink]="[getSearchLink()]"
[queryParams]="getAddParams(value.value) | async" queryParamsHandling="merge" > [queryParams]="getAddParams(value.value) | async" queryParamsHandling="merge" >
<input type="checkbox" [checked]="false" class="my-1 align-self-stretch"/> <input type="checkbox" [checked]="false" class="my-1 align-self-stretch"/>
<span class="filter-value px-1">{{value.value}}</span> <span class="filter-value px-1">{{value.value}}</span>
<span class="float-right filter-value-count ml-auto"> <span class="float-right filter-value-count ml-auto">
<span class="badge badge-secondary badge-pill">{{value.count}}</span> <span class="badge badge-secondary badge-pill">{{value.count}}</span>
</span> </span>
</a> </a>
</ng-container>
</ng-container> </ng-container>
</div>
</ng-container> </ng-container>
<div class="clearfix toggle-more-filters"> <div class="clearfix toggle-more-filters">
<a class="float-left" *ngIf="!(isLastPage$ | async)" <a class="float-left" *ngIf="!(isLastPage$ | async)"

View File

@@ -1,9 +1,12 @@
import { Component, OnInit } from '@angular/core'; import { animate, state, style, transition, trigger } from '@angular/animations';
import { FacetValue } from '../../../search-service/facet-value.model'; import { Component, HostBinding, OnInit } from '@angular/core';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { FilterType } from '../../../search-service/filter-type.model'; import { FilterType } from '../../../search-service/filter-type.model';
import {
facetLoad,
SearchFacetFilterComponent
} from '../search-facet-filter/search-facet-filter.component';
import { renderFacetFor } from '../search-filter-type-decorator'; import { renderFacetFor } from '../search-filter-type-decorator';
import { SearchFacetFilterComponent } from '../search-facet-filter/search-facet-filter.component';
/** /**
* This component renders a simple item page. * This component renders a simple item page.
@@ -15,6 +18,7 @@ import { SearchFacetFilterComponent } from '../search-facet-filter/search-facet-
selector: 'ds-search-text-filter', selector: 'ds-search-text-filter',
styleUrls: ['./search-text-filter.component.scss'], styleUrls: ['./search-text-filter.component.scss'],
templateUrl: './search-text-filter.component.html', templateUrl: './search-text-filter.component.html',
animations: [facetLoad]
}) })
@renderFacetFor(FilterType.text) @renderFacetFor(FilterType.text)

View File

@@ -1,5 +1,5 @@
<div> <div>
<label>{{ message }}</label> <label *ngIf="message">{{ message }}</label>
<div class="loader"> <div class="loader">
<span class="l-1"></span> <span class="l-1"></span>
<span class="l-2"></span> <span class="l-2"></span>