49440: date widget with decorator

This commit is contained in:
Lotte Hofstede
2018-02-28 14:05:28 +01:00
parent 60366e3eec
commit e7054af1d5
17 changed files with 208 additions and 10 deletions

View File

@@ -102,7 +102,9 @@
"jsonschema": "1.2.2",
"methods": "1.1.2",
"morgan": "1.9.0",
"ng2-nouislider": "^1.7.7",
"ngx-pagination": "3.0.3",
"nouislider": "^10.0.0",
"pem": "1.12.3",
"reflect-metadata": "0.1.12",
"rxjs": "5.5.6",

View File

@@ -0,0 +1 @@
<ng-container *ngComponentOutlet="getSearchFilter(); injector: objectInjector;"></ng-container>

View File

@@ -0,0 +1,36 @@
import { Component, Injector, Input, OnInit } from '@angular/core';
import { renderFilterType } from '../search-filter-type-decorator';
import { FilterType } from '../../../search-service/filter-type.model';
import { FacetValue } from '../../../search-service/facet-value.model';
import { SearchFilterConfig } from '../../../search-service/search-filter-config.model';
@Component({
selector: 'ds-search-facet-filter-wrapper',
templateUrl: './search-facet-filter-wrapper.component.html'
})
export class SearchFacetFilterWrapperComponent implements OnInit {
@Input() filterValues: FacetValue[];
@Input() filterConfig: SearchFilterConfig;
@Input() selectedValues: string[];
objectInjector: Injector;
constructor(private injector: Injector) {
}
ngOnInit(): void {
this.objectInjector = Injector.create({
providers: [
{ provide: 'filterValues', useFactory: () => (this.filterValues), deps: [] },
{ provide: 'filterConfig', useFactory: () => (this.filterConfig), deps: [] },
{ provide: 'selectedValues', useFactory: () => (this.selectedValues), deps: [] }],
parent: this.injector
});
}
getSearchFilter(): string {
const type: FilterType = this.filterConfig.type;
return renderFilterType(type);
}
}

View File

@@ -1,4 +1,4 @@
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
import { FacetValue } from '../../../search-service/facet-value.model';
import { SearchFilterConfig } from '../../../search-service/search-filter-config.model';
import { Router } from '@angular/router';
@@ -20,13 +20,10 @@ import { Subscription } from 'rxjs/Subscription';
@Component({
selector: 'ds-search-facet-filter',
styleUrls: ['./search-facet-filter.component.scss'],
templateUrl: './search-facet-filter.component.html'
template: ``,
})
export class SearchFacetFilterComponent implements OnInit, OnDestroy {
@Input() filterConfig: SearchFilterConfig;
@Input() selectedValues: string[];
filterValues: Array<Observable<RemoteData<PaginatedList<FacetValue>>>> = [];
filterValues$: BehaviorSubject<any> = new BehaviorSubject(this.filterValues);
currentPage: Observable<number>;
@@ -35,7 +32,11 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
pageChange = false;
sub: Subscription;
constructor(private searchService: SearchService, private filterService: SearchFilterService, private router: Router) {
constructor(private searchService: SearchService,
private filterService: SearchFilterService,
private router: Router,
@Inject('filterConfig') public filterConfig: SearchFilterConfig,
@Inject('selectedValues') public selectedValues: string[]) {
}
ngOnInit(): void {

View File

@@ -0,0 +1,17 @@
import { FilterType } from '../../search-service/filter-type.model';
const filterTypeMap = new Map();
export function renderFacetFor(type: FilterType) {
return function decorator(objectElement: any) {
if (!objectElement) {
return;
}
filterTypeMap.set(type, objectElement);
};
}
export function renderFilterType(type: FilterType) {
return filterTypeMap.get(type);
}

View File

@@ -2,6 +2,6 @@
<div (click)="toggle()" class="filter-name"><h5 class="d-inline-block mb-0">{{'search.filters.filter.' + filter.name + '.head'| translate}}</h5> <span class="filter-toggle fa float-right"
[ngClass]="(isCollapsed() | async) ? 'fa-plus' : 'fa-minus'"></span></div>
<div [@slide]="(isCollapsed() | async) ? 'collapsed' : 'expanded'" class="search-filter-wrapper">
<ds-search-facet-filter [filterConfig]="filter" [selectedValues]="getSelectedValues() | async"></ds-search-facet-filter>
<ds-search-facet-filter-wrapper [filterConfig]="filter" [selectedValues]="getSelectedValues() | async"></ds-search-facet-filter-wrapper>
</div>
</div>

View File

@@ -0,0 +1,21 @@
<div>
<div class="filters">
<form #form="ngForm" (ngSubmit)="onSubmit(form.value)" class="add-filter row"
[action]="getCurrentUrl()">
<input type="text" [(ngModel)]="min" [name]="filterConfig.paramName + 'min'" class="form-control col-6"
aria-label="Mininum value"
[placeholder]="'search.filters.filter.' + filterConfig.name + '.min.placeholder'| translate"/>
<input type="text" [(ngModel)]="max" [name]="filterConfig.paramName + 'max'" class="form-control col-6"
aria-label="Maximum value"
[placeholder]="'search.filters.filter.' + filterConfig.name + '.max.placeholder'| translate"/>
<input type="submit" class="d-none"/>
</form>
<nouislider [connect]="true" [min]="rangeMin" [max]="rangeMax" [step]="1" [(ngModel)]="range"></nouislider>
<a *ngFor="let value of selectedValues" class="d-block"
[routerLink]="[getSearchLink()]"
[queryParams]="getQueryParamsWithout(value) | async">
<input type="checkbox" [checked]="true"/>
<span class="filter-value">{{value}}</span>
</a>
</div>
</div>

View File

@@ -0,0 +1,33 @@
import { Component } from '@angular/core';
import { FilterType } from '../../../search-service/filter-type.model';
import { renderFacetFor } from '../search-filter-type-decorator';
import { SearchFacetFilterComponent } from '../search-facet-filter/search-facet-filter.component';
/**
* This component renders a simple item page.
* The route parameter 'id' is used to request the item it represents.
* All fields of the item that should be displayed, are defined in its template.
*/
@Component({
selector: 'ds-search-range-filter',
styleUrls: ['./search-range-filter.component.scss'],
templateUrl: './search-range-filter.component.html',
})
@renderFacetFor(FilterType.range)
export class SearchRangeFilterComponent extends SearchFacetFilterComponent {
min = 1950;
max = 1960;
rangeMin = 1900; // calculate using available values
rangeMax = 2000;
get range() {
return [this.min, this.max];
}
set range(value: number[]) {
this.min = value[0];
this.max = value[1];
}
}

View File

@@ -0,0 +1,18 @@
@import '../../../../../styles/variables.scss';
@import '../../../../../styles/mixins.scss';
.filters {
margin-top: $spacer/2;
margin-bottom: $spacer/2;
a {
color: $body-color;
&:hover {
text-decoration: none;
}
}
.toggle-more-filters a {
color: $link-color;
text-decoration: underline;
cursor: pointer;
}
}

View File

@@ -0,0 +1,51 @@
import { Component, OnInit } from '@angular/core';
import { FacetValue } from '../../../search-service/facet-value.model';
import { Observable } from 'rxjs/Observable';
import { FilterType } from '../../../search-service/filter-type.model';
import { renderFacetFor } from '../search-filter-type-decorator';
import { SearchFacetFilterComponent } from '../search-facet-filter/search-facet-filter.component';
/**
* This component renders a simple item page.
* The route parameter 'id' is used to request the item it represents.
* All fields of the item that should be displayed, are defined in its template.
*/
@Component({
selector: 'ds-search-text-filter',
styleUrls: ['./search-text-filter.component.scss'],
templateUrl: './search-text-filter.component.html',
})
@renderFacetFor(FilterType.text)
export class SearchTextFilterComponent extends SearchFacetFilterComponent implements OnInit {
currentPage: Observable<number>;
ngOnInit(): void {
this.currentPage = this.filterService.getPage(this.filterConfig.name);
}
isChecked(value: FacetValue): Observable<boolean> {
return this.filterService.isFilterActiveWithValue(this.filterConfig.paramName, value.value);
}
get facetCount(): Observable<number> {
const resultCount = this.filterValues.length;
return this.currentPage.map((page: number) => {
const max = page * this.filterConfig.pageSize;
return max > resultCount ? resultCount : max;
});
}
showMore() {
this.filterService.incrementPage(this.filterConfig.name);
}
showFirstPageOnly() {
this.filterService.resetPage(this.filterConfig.name);
}
getCurrentPage(): Observable<number> {
return this.filterService.getPage(this.filterConfig.name);
}
}

View File

@@ -20,6 +20,9 @@ import { SearchFiltersComponent } from './search-filters/search-filters.componen
import { SearchFilterComponent } from './search-filters/search-filter/search-filter.component';
import { SearchFacetFilterComponent } from './search-filters/search-filter/search-facet-filter/search-facet-filter.component';
import { SearchFilterService } from './search-filters/search-filter/search-filter.service';
import { SearchDateFilterComponent } from './search-filters/search-filter/search-date-filter/search-date-filter.component';
import { SearchTextFilterComponent } from './search-filters/search-filter/search-text-filter/search-text-filter.component';
import { SearchFacetFilterWrapperComponent } from './search-filters/search-filter/search-facet-filter-wrapper/search-facet-filter-wrapper.component';
const effects = [
SearchSidebarEffects
@@ -46,7 +49,10 @@ const effects = [
CommunitySearchResultListElementComponent,
SearchFiltersComponent,
SearchFilterComponent,
SearchFacetFilterComponent
SearchFacetFilterComponent,
SearchFacetFilterWrapperComponent,
SearchDateFilterComponent,
SearchTextFilterComponent,
],
providers: [
SearchService,
@@ -60,6 +66,8 @@ const effects = [
ItemSearchResultGridElementComponent,
CollectionSearchResultGridElementComponent,
CommunitySearchResultGridElementComponent,
SearchDateFilterComponent,
SearchTextFilterComponent,
]
})
export class SearchPageModule {

View File

@@ -1,5 +1,6 @@
@import '../styles/variables.scss';
@import '../../node_modules/bootstrap/scss/bootstrap.scss';
@import '../../node_modules/nouislider/distribute/nouislider.min.css';
@import "../../node_modules/font-awesome/scss/font-awesome.scss";
html {

View File

@@ -2,7 +2,7 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { NouisliderModule } from 'ng2-nouislider';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { TranslateModule } from '@ngx-translate/core';
@@ -54,7 +54,8 @@ const MODULES = [
NgxPaginationModule,
ReactiveFormsModule,
RouterModule,
TranslateModule
TranslateModule,
NouisliderModule
];
const PIPES = [

View File

@@ -5581,6 +5581,10 @@ netmask@~1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/netmask/-/netmask-1.0.6.tgz#20297e89d86f6f6400f250d9f4f6b4c1945fcd35"
ng2-nouislider@^1.7.7:
version "1.7.7"
resolved "https://registry.yarnpkg.com/ng2-nouislider/-/ng2-nouislider-1.7.7.tgz#b841f4b313c8c9c8a763c80f3a59d5aa4c3a70c8"
ngrx-store-freeze@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/ngrx-store-freeze/-/ngrx-store-freeze-0.2.1.tgz#04fb29db33cafda0f2d6ea32adeaac4891b1b27b"
@@ -5800,6 +5804,10 @@ normalize-url@^1.4.0:
query-string "^4.1.0"
sort-keys "^1.0.0"
nouislider@^10.0.0:
version "10.1.0"
resolved "https://registry.yarnpkg.com/nouislider/-/nouislider-10.1.0.tgz#7bdd0411fd62d4584bfe88cb92bb8d06e64c6b47"
npm-run-all@4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.2.tgz#90d62d078792d20669139e718621186656cea056"