mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-17 06:53:03 +00:00
45621: ngrx filter facets
This commit is contained in:
@@ -96,14 +96,18 @@
|
|||||||
"reset": "Reset filters",
|
"reset": "Reset filters",
|
||||||
"facet-filter": {
|
"facet-filter": {
|
||||||
"show-more": "Show more",
|
"show-more": "Show more",
|
||||||
|
"show-less": "Show less",
|
||||||
"author": {
|
"author": {
|
||||||
"placeholder": "Author name"
|
"placeholder": "Author name",
|
||||||
|
"head": "Author"
|
||||||
},
|
},
|
||||||
"scope": {
|
"scope": {
|
||||||
"placeholder": "Scope filter"
|
"placeholder": "Scope filter",
|
||||||
|
"head": "Scope"
|
||||||
},
|
},
|
||||||
"subject": {
|
"subject": {
|
||||||
"placeholder": "Subject"
|
"placeholder": "Subject",
|
||||||
|
"head": "Subject"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,15 @@
|
|||||||
<a *ngFor="let value of filterValues" class="d-block" [routerLink]="[getSearchLink()]" [queryParams]="getQueryParams(value) | async">
|
<a *ngFor="let value of filterValues; let i=index" class="d-block" [routerLink]="[getSearchLink()]"
|
||||||
<input type="checkbox" [checked]="isChecked(value)"/>
|
[queryParams]="getQueryParams(value)">
|
||||||
<span class="filter-value">{{value.value}}</span>
|
<ng-template [ngIf]="i < (facetCount | async)">
|
||||||
<span class="filter-value-count float-right">({{value.count}})</span>
|
<input type="checkbox" [checked]="isChecked(value)"/>
|
||||||
|
<span class="filter-value">{{value.value}}</span>
|
||||||
|
<span class="filter-value-count float-right">({{value.count}})</span>
|
||||||
|
</ng-template>
|
||||||
</a>
|
</a>
|
||||||
<a href="">{{"search.filters.facet-filter.show-more" | translate}}</a>
|
<a *ngIf="filterValues.length > (facetCount | async)" (click)="showMore()">{{"search.filters.facet-filter.show-more"
|
||||||
|
| translate}}</a>
|
||||||
|
<a *ngIf="(currentPage | async) > 1" (click)="showLess()">{{"search.filters.facet-filter.show-less" |
|
||||||
|
translate}}</a>
|
||||||
|
|
||||||
<input type="text" [placeholder]="'search.filters.facet-filter.' + filterConfig.name + '.placeholder'| translate"/>
|
<input type="text"
|
||||||
|
[placeholder]="'search.filters.facet-filter.' + filterConfig.name + '.placeholder'| translate"/>
|
@@ -1,9 +1,10 @@
|
|||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
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 { SearchService } from '../../../search-service/search.service';
|
import { SearchService } from '../../../search-service/search.service';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { Params } from '@angular/router';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import { SearchFilterService } from '../search-filter.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component renders a simple item page.
|
* This component renders a simple item page.
|
||||||
@@ -17,25 +18,48 @@ import { Observable } from 'rxjs/Observable';
|
|||||||
templateUrl: './search-facet-filter.component.html',
|
templateUrl: './search-facet-filter.component.html',
|
||||||
})
|
})
|
||||||
|
|
||||||
export class SidebarFacetFilterComponent {
|
export class SidebarFacetFilterComponent implements OnInit {
|
||||||
@Input() filterValues: FacetValue[];
|
@Input() filterValues: FacetValue[];
|
||||||
@Input() filterConfig: SearchFilterConfig;
|
@Input() filterConfig: SearchFilterConfig;
|
||||||
|
currentPage: Observable<number>;
|
||||||
|
|
||||||
constructor(private searchService: SearchService, private route: ActivatedRoute) {
|
constructor(private filterService: SearchFilterService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.currentPage = this.filterService.getPage(this.filterConfig.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
isChecked(value: FacetValue) {
|
isChecked(value: FacetValue) {
|
||||||
return this.searchService.isFilterActive(this.filterConfig.name, value.value);
|
return this.filterService.isFilterActive(this.filterConfig.name, value.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
getSearchLink() {
|
getSearchLink() {
|
||||||
return this.searchService.getSearchLink();
|
return this.filterService.searchLink;
|
||||||
}
|
}
|
||||||
|
|
||||||
getQueryParams(value: FacetValue): Observable<any> {
|
getQueryParams(value: FacetValue): Params {
|
||||||
const params = {};
|
return this.filterService.switchFilterInURL(this.filterConfig, value.value);
|
||||||
params[this.filterConfig.paramName] = value.value;
|
|
||||||
return this.route.queryParams.map((p) => Object.assign({}, p, params))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.increasePage(this.filterConfig.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
showLess() {
|
||||||
|
this.filterService.decreasePage(this.filterConfig.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentPage(): Observable<number> {
|
||||||
|
return this.filterService.getPage(this.filterConfig.name);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,58 @@
|
|||||||
|
import { Action } from '@ngrx/store';
|
||||||
|
|
||||||
|
import { type } from '../../../shared/ngrx/type';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For each action type in an action group, make a simple
|
||||||
|
* enum object for all of this group's action types.
|
||||||
|
*
|
||||||
|
* The 'type' utility function coerces strings into string
|
||||||
|
* literal types and runs a simple check to guarantee all
|
||||||
|
* action types in the application are unique.
|
||||||
|
*/
|
||||||
|
export const SearchFilterActionTypes = {
|
||||||
|
COLLAPSE: type('dspace/search-filter/COLLAPSE'),
|
||||||
|
INITIAL_COLLAPSE: type('dspace/search-filter/INITIAL_COLLAPSE'),
|
||||||
|
EXPAND: type('dspace/search-filter/EXPAND'),
|
||||||
|
INITIAL_EXPAND: type('dspace/search-filter/INITIAL_EXPAND'),
|
||||||
|
TOGGLE: type('dspace/search-filter/TOGGLE'),
|
||||||
|
DECREASE_PAGE: type('dspace/search-filter/DECREASE_PAGE'),
|
||||||
|
INCREASE_PAGE: type('dspace/search-filter/INCREASE_PAGE')
|
||||||
|
};
|
||||||
|
|
||||||
|
export class SearchFilterAction implements Action {
|
||||||
|
filterName: string;
|
||||||
|
type;
|
||||||
|
constructor(name: string) {
|
||||||
|
this.filterName = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* tslint:disable:max-classes-per-file */
|
||||||
|
export class SearchFilterCollapseAction extends SearchFilterAction {
|
||||||
|
type = SearchFilterActionTypes.COLLAPSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SearchFilterExpandAction extends SearchFilterAction {
|
||||||
|
type = SearchFilterActionTypes.EXPAND;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SearchFilterToggleAction extends SearchFilterAction {
|
||||||
|
type = SearchFilterActionTypes.TOGGLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SearchFilterInitialCollapseAction extends SearchFilterAction {
|
||||||
|
type = SearchFilterActionTypes.INITIAL_COLLAPSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SearchFilterInitialExpandAction extends SearchFilterAction {
|
||||||
|
type = SearchFilterActionTypes.INITIAL_EXPAND;
|
||||||
|
}
|
||||||
|
export class SearchFilterDecreasePageAction extends SearchFilterAction {
|
||||||
|
type = SearchFilterActionTypes.DECREASE_PAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SearchFilterIncreasePageAction extends SearchFilterAction {
|
||||||
|
type = SearchFilterActionTypes.INCREASE_PAGE;
|
||||||
|
}
|
||||||
|
/* tslint:enable:max-classes-per-file */
|
@@ -1,4 +1,8 @@
|
|||||||
<div>
|
<div>
|
||||||
<div (click)="toggle()" class="filter-name">{{filter.name}} <span class="fa float-right" [ngClass]="isCollapsed ? 'fa-plus' : 'fa-minus'"></span></div>
|
<div (click)="toggle()" class="filter-name">{{filter.name}} <span class="fa float-right"
|
||||||
<ds-search-facet-filter [filterConfig]="filter" [filterValues]="filterValues.payload | async"></ds-search-facet-filter>
|
[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"
|
||||||
|
[filterValues]="filterValues.payload | async"></ds-search-facet-filter>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
@@ -1,2 +1,6 @@
|
|||||||
@import '../../../../styles/variables.scss';
|
@import '../../../../styles/variables.scss';
|
||||||
@import '../../../../styles/mixins.scss';
|
@import '../../../../styles/mixins.scss';
|
||||||
|
|
||||||
|
.search-filter-wrapper {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
@@ -3,6 +3,9 @@ import { SearchFilterConfig } from '../../search-service/search-filter-config.mo
|
|||||||
import { SearchService } from '../../search-service/search.service';
|
import { SearchService } from '../../search-service/search.service';
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
import { FacetValue } from '../../search-service/facet-value.model';
|
import { FacetValue } from '../../search-service/facet-value.model';
|
||||||
|
import { SearchFilterService } from './search-filter.service';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import { slide } from '../../../shared/animations/slide';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component renders a simple item page.
|
* This component renders a simple item page.
|
||||||
@@ -14,21 +17,38 @@ import { FacetValue } from '../../search-service/facet-value.model';
|
|||||||
selector: 'ds-search-filter',
|
selector: 'ds-search-filter',
|
||||||
styleUrls: ['./search-filter.component.scss'],
|
styleUrls: ['./search-filter.component.scss'],
|
||||||
templateUrl: './search-filter.component.html',
|
templateUrl: './search-filter.component.html',
|
||||||
|
animations: [slide]
|
||||||
})
|
})
|
||||||
|
|
||||||
export class SidebarFilterComponent implements OnInit {
|
export class SidebarFilterComponent implements OnInit {
|
||||||
@Input() filter: SearchFilterConfig;
|
@Input() filter: SearchFilterConfig;
|
||||||
filterValues: RemoteData<FacetValue[]>;
|
filterValues: RemoteData<FacetValue[]>;
|
||||||
isCollapsed = false;
|
|
||||||
|
|
||||||
constructor(private searchService: SearchService) {
|
constructor(private searchService: SearchService, private filterService: SearchFilterService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.filterValues = this.searchService.getFacetValuesFor(this.filter.name);
|
this.filterValues = this.searchService.getFacetValuesFor(this.filter.name);
|
||||||
|
if (this.filter.isOpenByDefault) {
|
||||||
|
this.initialExpand();
|
||||||
|
} else {
|
||||||
|
this.initialCollapse();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toggle() {
|
toggle() {
|
||||||
this.isCollapsed = !this.isCollapsed;
|
this.filterService.toggle(this.filter.name);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
isCollapsed(): Observable<boolean> {
|
||||||
|
return this.filterService.isCollapsed(this.filter.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
initialCollapse() {
|
||||||
|
this.filterService.initialCollapse(this.filter.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
initialExpand() {
|
||||||
|
this.filterService.initialExpand(this.filter.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -0,0 +1,95 @@
|
|||||||
|
import { SearchFilterAction, SearchFilterActionTypes } from './search-filter.actions';
|
||||||
|
import { isEmpty } from '../../../shared/empty.util';
|
||||||
|
|
||||||
|
export interface SearchFilterState {
|
||||||
|
filterCollapsed: boolean,
|
||||||
|
page: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SearchFiltersState {
|
||||||
|
[name: string]: SearchFilterState
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: SearchFiltersState = Object.create(null);
|
||||||
|
|
||||||
|
export function filterReducer(state = initialState, action: SearchFilterAction): SearchFiltersState {
|
||||||
|
|
||||||
|
switch (action.type) {
|
||||||
|
|
||||||
|
case SearchFilterActionTypes.INITIAL_COLLAPSE: {
|
||||||
|
if (isEmpty(state) || isEmpty(state[action.filterName])) {
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
[action.filterName]: {
|
||||||
|
filterCollapsed: true,
|
||||||
|
page: 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SearchFilterActionTypes.INITIAL_EXPAND: {
|
||||||
|
if (isEmpty(state) || isEmpty(state[action.filterName])) {
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
[action.filterName]: {
|
||||||
|
filterCollapsed: false,
|
||||||
|
page: 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SearchFilterActionTypes.COLLAPSE: {
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
[action.filterName]: {
|
||||||
|
filterCollapsed: true,
|
||||||
|
page: state[action.filterName].page
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
case SearchFilterActionTypes.EXPAND: {
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
[action.filterName]: {
|
||||||
|
filterCollapsed: false,
|
||||||
|
page: state[action.filterName].page
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
case SearchFilterActionTypes.DECREASE_PAGE: {
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
[action.filterName]: {
|
||||||
|
filterCollapsed: state[action.filterName].filterCollapsed,
|
||||||
|
page: state[action.filterName].page - 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
case SearchFilterActionTypes.INCREASE_PAGE: {
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
[action.filterName]: {
|
||||||
|
filterCollapsed: state[action.filterName].filterCollapsed,
|
||||||
|
page: state[action.filterName].page + 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
case SearchFilterActionTypes.TOGGLE: {
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
[action.filterName]: {
|
||||||
|
filterCollapsed: !state[action.filterName].filterCollapsed,
|
||||||
|
page: state[action.filterName].page
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,136 @@
|
|||||||
|
import { Injectable, OnDestroy } from '@angular/core';
|
||||||
|
import { SearchFiltersState, SearchFilterState } from './search-filter.reducer';
|
||||||
|
import { createSelector, MemoizedSelector, Store } from '@ngrx/store';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import { AppState } from '../../../app.reducer';
|
||||||
|
import {
|
||||||
|
SearchFilterCollapseAction, SearchFilterDecreasePageAction, SearchFilterIncreasePageAction,
|
||||||
|
SearchFilterInitialCollapseAction,
|
||||||
|
SearchFilterInitialExpandAction,
|
||||||
|
SearchFilterToggleAction
|
||||||
|
} from './search-filter.actions';
|
||||||
|
import { hasValue, isNotEmpty } from '../../../shared/empty.util';
|
||||||
|
import { ActivatedRoute, Params, Router } from '@angular/router';
|
||||||
|
import { SearchFilterConfig } from '../../search-service/search-filter-config.model';
|
||||||
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
|
import { PageInfo } from '../../../core/shared/page-info.model';
|
||||||
|
import { FacetValue } from '../../search-service/facet-value.model';
|
||||||
|
import { FilterType } from '../../search-service/filter-type.model';
|
||||||
|
import { SearchService } from '../../search-service/search.service';
|
||||||
|
|
||||||
|
const filterStateSelector = (state: SearchFiltersState) => state.searchFilter;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SearchFilterService implements OnDestroy {
|
||||||
|
private sub;
|
||||||
|
|
||||||
|
constructor(private store: Store<SearchFiltersState>,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private router: Router,
|
||||||
|
private searchService: SearchService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
isFilterActive(filterName: string, filterValue: string): boolean {
|
||||||
|
let filterConfig: SearchFilterConfig;
|
||||||
|
this.sub = this.searchService.getConfig().payload
|
||||||
|
.subscribe((configuration) => filterConfig = configuration
|
||||||
|
.find((config: SearchFilterConfig) => config.name === filterName));
|
||||||
|
return isNotEmpty(this.route.snapshot.queryParams[filterConfig.paramName]) && [...this.route.snapshot.queryParams[filterConfig.paramName]].indexOf(filterValue, 0) > -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
switchFilterInURL(filterConfig: SearchFilterConfig, value: string) {
|
||||||
|
console.log(this.route.snapshot.queryParams);
|
||||||
|
if (this.isFilterActive(filterConfig.name, value)) {
|
||||||
|
return this.removeQueryParameter(filterConfig.paramName, value);
|
||||||
|
} else {
|
||||||
|
return this.addQueryParameter(filterConfig.paramName, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addQueryParameter(paramName: string, value: string): Params {
|
||||||
|
const currentParams = this.route.snapshot.queryParams;
|
||||||
|
const newParam = {};
|
||||||
|
if ((currentParams[paramName])) {
|
||||||
|
newParam[paramName] = [...currentParams[paramName], value];
|
||||||
|
} else {
|
||||||
|
newParam[paramName] = [value];
|
||||||
|
}
|
||||||
|
return Object.assign({}, currentParams, newParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeQueryParameter(paramName: string, value: string): Params {
|
||||||
|
const currentParams = this.route.snapshot.queryParams;
|
||||||
|
const newParam = {};
|
||||||
|
let currentFilterParams = [...currentParams[paramName]];
|
||||||
|
if (isNotEmpty(currentFilterParams)) {
|
||||||
|
const index = currentFilterParams.indexOf(value, 0);
|
||||||
|
if (index > -1) {
|
||||||
|
currentFilterParams = currentFilterParams.splice(index, 1);
|
||||||
|
}
|
||||||
|
newParam[paramName] = currentFilterParams;
|
||||||
|
}
|
||||||
|
return Object.assign({}, currentParams, newParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
get searchLink() {
|
||||||
|
return this.searchService.searchLink;
|
||||||
|
}
|
||||||
|
|
||||||
|
isCollapsed(filterName: string): Observable<boolean> {
|
||||||
|
return this.store.select(filterByNameSelector(filterName))
|
||||||
|
.map((object: SearchFilterState) => object.filterCollapsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
getPage(filterName: string): Observable<number> {
|
||||||
|
return this.store.select(filterByNameSelector(filterName))
|
||||||
|
.map((object: SearchFilterState) => object.page);
|
||||||
|
}
|
||||||
|
|
||||||
|
public collapse(filterName: string): void {
|
||||||
|
this.store.dispatch(new SearchFilterCollapseAction(filterName));
|
||||||
|
}
|
||||||
|
|
||||||
|
public expand(filterName: string): void {
|
||||||
|
this.store.dispatch(new SearchFilterCollapseAction(filterName));
|
||||||
|
}
|
||||||
|
|
||||||
|
public toggle(filterName: string): void {
|
||||||
|
this.store.dispatch(new SearchFilterToggleAction(filterName));
|
||||||
|
}
|
||||||
|
|
||||||
|
public initialCollapse(filterName: string): void {
|
||||||
|
this.store.dispatch(new SearchFilterInitialCollapseAction(filterName));
|
||||||
|
}
|
||||||
|
|
||||||
|
public initialExpand(filterName: string): void {
|
||||||
|
this.store.dispatch(new SearchFilterInitialExpandAction(filterName));
|
||||||
|
}
|
||||||
|
|
||||||
|
public decreasePage(filterName: string): void {
|
||||||
|
this.store.dispatch(new SearchFilterDecreasePageAction(filterName));
|
||||||
|
}
|
||||||
|
|
||||||
|
public increasePage(filterName: string): void {
|
||||||
|
this.store.dispatch(new SearchFilterIncreasePageAction(filterName));
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
if (this.sub !== undefined) {
|
||||||
|
this.sub.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterByNameSelector(name: string): MemoizedSelector<SearchFiltersState, SearchFilterState> {
|
||||||
|
return keySelector<SearchFilterState>(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function keySelector<T>(key: string): MemoizedSelector<SearchFiltersState, T> {
|
||||||
|
return createSelector(filterStateSelector, (state: SearchFilterState) => {
|
||||||
|
if (hasValue(state)) {
|
||||||
|
return state[key];
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
@@ -13,7 +13,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div id="search-body"
|
<div id="search-body"
|
||||||
class="row-offcanvas row-offcanvas-left"
|
class="row-offcanvas row-offcanvas-left"
|
||||||
[@slideInOut]="(isSidebarCollapsed() | async) ? 'collapsed' : 'expanded'">
|
[@pushInOut]="(isSidebarCollapsed() | async) ? 'collapsed' : 'expanded'">
|
||||||
<ds-search-sidebar *ngIf="(isMobileView | async)" class="col-12"
|
<ds-search-sidebar *ngIf="(isMobileView | async)" class="col-12"
|
||||||
id="search-sidebar-xs"
|
id="search-sidebar-xs"
|
||||||
resultCount="{{(results.pageInfo | async)?.totalElements}}"
|
resultCount="{{(results.pageInfo | async)?.totalElements}}"
|
||||||
|
@@ -11,7 +11,7 @@ import { CommunityDataService } from '../core/data/community-data.service';
|
|||||||
import { isNotEmpty } from '../shared/empty.util';
|
import { isNotEmpty } from '../shared/empty.util';
|
||||||
import { Community } from '../core/shared/community.model';
|
import { Community } from '../core/shared/community.model';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
import { slideInOut } from '../shared/animations/slide';
|
import { pushInOut } from '../shared/animations/push';
|
||||||
import { HostWindowService } from '../shared/host-window.service';
|
import { HostWindowService } from '../shared/host-window.service';
|
||||||
import { SearchSidebarService } from './search-sidebar/search-sidebar.service';
|
import { SearchSidebarService } from './search-sidebar/search-sidebar.service';
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ import { SearchSidebarService } from './search-sidebar/search-sidebar.service';
|
|||||||
selector: 'ds-search-page',
|
selector: 'ds-search-page',
|
||||||
styleUrls: ['./search-page.component.scss'],
|
styleUrls: ['./search-page.component.scss'],
|
||||||
templateUrl: './search-page.component.html',
|
templateUrl: './search-page.component.html',
|
||||||
animations: [slideInOut]
|
animations: [pushInOut]
|
||||||
})
|
})
|
||||||
export class SearchPageComponent implements OnInit, OnDestroy {
|
export class SearchPageComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
@@ -15,6 +15,7 @@ import { EffectsModule } from '@ngrx/effects';
|
|||||||
import { SidebarFiltersComponent } from './search-filters/search-filters.component';
|
import { SidebarFiltersComponent } from './search-filters/search-filters.component';
|
||||||
import { SidebarFilterComponent } from './search-filters/search-filter/search-filter.component';
|
import { SidebarFilterComponent } from './search-filters/search-filter/search-filter.component';
|
||||||
import { SidebarFacetFilterComponent } from './search-filters/search-filter/search-facet-filter/search-facet-filter.component';
|
import { SidebarFacetFilterComponent } from './search-filters/search-filter/search-facet-filter/search-facet-filter.component';
|
||||||
|
import { SearchFilterService } from './search-filters/search-filter/search-filter.service';
|
||||||
|
|
||||||
const effects = [
|
const effects = [
|
||||||
SearchSidebarEffects
|
SearchSidebarEffects
|
||||||
@@ -40,7 +41,8 @@ const effects = [
|
|||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
SearchService,
|
SearchService,
|
||||||
SearchSidebarService
|
SearchSidebarService,
|
||||||
|
SearchFilterService
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
ItemSearchResultListElementComponent,
|
ItemSearchResultListElementComponent,
|
||||||
|
@@ -5,6 +5,7 @@ export class SearchFilterConfig {
|
|||||||
name: string;
|
name: string;
|
||||||
type: FilterType;
|
type: FilterType;
|
||||||
hasFacets: boolean;
|
hasFacets: boolean;
|
||||||
|
pageSize = 3;
|
||||||
isOpenByDefault: boolean;
|
isOpenByDefault: boolean;
|
||||||
/**
|
/**
|
||||||
* Name of this configuration that can be used in a url
|
* Name of this configuration that can be used in a url
|
||||||
|
@@ -235,11 +235,6 @@ export class SearchService implements OnDestroy {
|
|||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
isFilterActive(filterName: string, filterValue: string): boolean {
|
|
||||||
const filterConfig = this.config.find((config: SearchFilterConfig) => config.name === filterName);
|
|
||||||
return isNotEmpty(this.router.url.match(filterConfig.paramName + '=' + encodeURI(filterValue) + '(&(.*))?$'));
|
|
||||||
}
|
|
||||||
|
|
||||||
getSearchLink() {
|
getSearchLink() {
|
||||||
return this.searchLink;
|
return this.searchLink;
|
||||||
}
|
}
|
||||||
@@ -249,5 +244,4 @@ export class SearchService implements OnDestroy {
|
|||||||
this.sub.unsubscribe();
|
this.sub.unsubscribe();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,6 @@ import { Injectable } from '@angular/core';
|
|||||||
import { Effect, Actions } from '@ngrx/effects'
|
import { Effect, Actions } from '@ngrx/effects'
|
||||||
import * as fromRouter from '@ngrx/router-store';
|
import * as fromRouter from '@ngrx/router-store';
|
||||||
|
|
||||||
import { HostWindowActionTypes } from '../../shared/host-window.actions';
|
|
||||||
import { SearchSidebarCollapseAction } from './search-sidebar.actions';
|
import { SearchSidebarCollapseAction } from './search-sidebar.actions';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@@ -7,12 +7,17 @@ import {
|
|||||||
SearchSidebarState,
|
SearchSidebarState,
|
||||||
sidebarReducer
|
sidebarReducer
|
||||||
} from './+search-page/search-sidebar/search-sidebar.reducer';
|
} from './+search-page/search-sidebar/search-sidebar.reducer';
|
||||||
|
import {
|
||||||
|
filterReducer,
|
||||||
|
SearchFiltersState
|
||||||
|
} from './+search-page/search-filters/search-filter/search-filter.reducer';
|
||||||
|
|
||||||
export interface AppState {
|
export interface AppState {
|
||||||
router: fromRouter.RouterReducerState;
|
router: fromRouter.RouterReducerState;
|
||||||
hostWindow: HostWindowState;
|
hostWindow: HostWindowState;
|
||||||
header: HeaderState;
|
header: HeaderState;
|
||||||
searchSidebar: SearchSidebarState;
|
searchSidebar: SearchSidebarState;
|
||||||
|
searchFilter: SearchFiltersState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const appReducers: ActionReducerMap<AppState> = {
|
export const appReducers: ActionReducerMap<AppState> = {
|
||||||
@@ -20,4 +25,5 @@ export const appReducers: ActionReducerMap<AppState> = {
|
|||||||
hostWindow: hostWindowReducer,
|
hostWindow: hostWindowReducer,
|
||||||
header: headerReducer,
|
header: headerReducer,
|
||||||
searchSidebar: sidebarReducer,
|
searchSidebar: sidebarReducer,
|
||||||
|
searchFilter: filterReducer
|
||||||
};
|
};
|
||||||
|
16
src/app/shared/animations/push.ts
Normal file
16
src/app/shared/animations/push.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { animate, state, transition, trigger, style } from '@angular/animations';
|
||||||
|
|
||||||
|
export const pushInOut = trigger('pushInOut', [
|
||||||
|
|
||||||
|
/*
|
||||||
|
state('expanded', style({ right: '100%' }));
|
||||||
|
|
||||||
|
state('collapsed', style({ right: 0 }));
|
||||||
|
*/
|
||||||
|
|
||||||
|
state('expanded', style({ left: '100%' })),
|
||||||
|
|
||||||
|
state('collapsed', style({ left: 0 })),
|
||||||
|
|
||||||
|
transition('expanded <=> collapsed', animate(250)),
|
||||||
|
]);
|
@@ -1,16 +1,10 @@
|
|||||||
import { animate, state, transition, trigger, style } from '@angular/animations';
|
import { animate, state, transition, trigger, style } from '@angular/animations';
|
||||||
|
|
||||||
export const slideInOut = trigger('slideInOut', [
|
export const slide = trigger('slide', [
|
||||||
|
|
||||||
/*
|
state('expanded', style({ height: '*' })),
|
||||||
state('expanded', style({ right: '100%' }));
|
|
||||||
|
|
||||||
state('collapsed', style({ right: 0 }));
|
state('collapsed', style({ height: 0 })),
|
||||||
*/
|
|
||||||
|
|
||||||
state('expanded', style({ left: '100%' })),
|
|
||||||
|
|
||||||
state('collapsed', style({ left: 0 })),
|
|
||||||
|
|
||||||
transition('expanded <=> collapsed', animate(250)),
|
transition('expanded <=> collapsed', animate(250)),
|
||||||
]);
|
]);
|
||||||
|
Reference in New Issue
Block a user