song list filter
This commit is contained in:
6
src/app/modules/songs/song-list/filter/filter-values.ts
Normal file
6
src/app/modules/songs/song-list/filter/filter-values.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export interface FilterValues {
|
||||
q: string;
|
||||
type: string;
|
||||
legalType: string;
|
||||
flag: string;
|
||||
}
|
||||
35
src/app/modules/songs/song-list/filter/filter.component.html
Normal file
35
src/app/modules/songs/song-list/filter/filter.component.html
Normal file
@@ -0,0 +1,35 @@
|
||||
<div [formGroup]="filterFormGroup">
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Titel oder Text</mat-label>
|
||||
<input formControlName="q" matInput>
|
||||
</mat-form-field>
|
||||
|
||||
<div class="third">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Typ</mat-label>
|
||||
<mat-select formControlName="type">
|
||||
<mat-option [value]="null">- kein Filter -</mat-option>
|
||||
<mat-option *ngFor="let type of types" [value]="type">{{type | songType}}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Rechtlicher Status</mat-label>
|
||||
<mat-select formControlName="legalType">
|
||||
<mat-option [value]="null">- kein Filter -</mat-option>
|
||||
<mat-option *ngFor="let key of legalType" [value]="key">{{key|legalType}}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Attribute</mat-label>
|
||||
<mat-select formControlName="flag">
|
||||
<mat-option [value]="null">- kein Filter -</mat-option>
|
||||
<mat-option *ngFor="let flag of getFlags()" [value]="flag">{{flag}}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<i>Anzahl der Suchergebnisse: {{songs.length}}</i>
|
||||
</div>
|
||||
@@ -0,0 +1,5 @@
|
||||
.third {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
column-gap: 20px;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {FilterComponent} from './filter.component';
|
||||
|
||||
describe('FilterComponent', () => {
|
||||
let component: FilterComponent;
|
||||
let fixture: ComponentFixture<FilterComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [FilterComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(FilterComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
63
src/app/modules/songs/song-list/filter/filter.component.ts
Normal file
63
src/app/modules/songs/song-list/filter/filter.component.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import {Component, Input, OnInit} from '@angular/core';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {FormBuilder, FormGroup} from '@angular/forms';
|
||||
import {SongService} from '../../services/song.service';
|
||||
import {FilterValues} from './filter-values';
|
||||
import {Song} from '../../services/song';
|
||||
|
||||
@Component({
|
||||
selector: 'app-filter',
|
||||
templateUrl: './filter.component.html',
|
||||
styleUrls: ['./filter.component.less']
|
||||
})
|
||||
export class FilterComponent implements OnInit {
|
||||
|
||||
public filterFormGroup: FormGroup;
|
||||
@Input() route: string;
|
||||
@Input() songs: Song[];
|
||||
public types = SongService.TYPES;
|
||||
public legalType = SongService.LEGAL_TYPE;
|
||||
|
||||
constructor(private router: Router, activatedRoute: ActivatedRoute, fb: FormBuilder) {
|
||||
this.filterFormGroup = fb.group({
|
||||
q: '',
|
||||
type: '',
|
||||
legalType: '',
|
||||
flag: '',
|
||||
});
|
||||
|
||||
activatedRoute.queryParams.subscribe((filterValues: FilterValues) => {
|
||||
if (filterValues.q) this.filterFormGroup.controls.q.setValue(filterValues.q);
|
||||
if (filterValues.type) this.filterFormGroup.controls.type.setValue(filterValues.type);
|
||||
if (filterValues.legalType) this.filterFormGroup.controls.legalType.setValue(filterValues.legalType);
|
||||
if (filterValues.flag) this.filterFormGroup.controls.flag.setValue(filterValues.flag);
|
||||
})
|
||||
|
||||
this.filterFormGroup.controls.q.valueChanges.subscribe(_ => this.filerValueChanged('q', _));
|
||||
this.filterFormGroup.controls.type.valueChanges.subscribe(_ => this.filerValueChanged('type', _));
|
||||
this.filterFormGroup.controls.legalType.valueChanges.subscribe(_ => this.filerValueChanged('legalType', _));
|
||||
this.filterFormGroup.controls.flag.valueChanges.subscribe(_ => this.filerValueChanged('flag', _));
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
public getFlags(): string[] {
|
||||
const flags = this.songs
|
||||
.map(_ => _.flags)
|
||||
.filter(_ => !!_)
|
||||
.map(_ => _.split(';'))
|
||||
.reduce((pn, u) => [...pn, ...u], [])
|
||||
.filter(_ => !!_);
|
||||
|
||||
const uqFlags = flags.filter((n, i) => flags.indexOf(n) === i);
|
||||
|
||||
return uqFlags;
|
||||
}
|
||||
|
||||
private async filerValueChanged(key: string, value: string): Promise<void> {
|
||||
const route = this.router.createUrlTree([this.route], {queryParams: {[key]: value}, queryParamsHandling: 'merge'});
|
||||
await this.router.navigateByUrl(route);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
<div class="list-item">
|
||||
<div class="number">{{song.number}}</div>
|
||||
<div>{{song.title}}</div>
|
||||
<div>
|
||||
<span *ngIf="song.legalType==='open'" class="warning"><fa-icon [icon]="faLegal"></fa-icon> </span>
|
||||
{{song.title}}</div>
|
||||
<div>{{song.key}}</div>
|
||||
<div>{{song.legalType | legalType}}</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
.list-item {
|
||||
padding: 5px 20px;
|
||||
display: grid;
|
||||
grid-template-columns: 50px auto 30px 100px;
|
||||
grid-template-columns: 50px auto 30px;
|
||||
|
||||
& > div {
|
||||
display: flex;
|
||||
@@ -15,6 +15,10 @@
|
||||
&:hover {
|
||||
background: @primary-color;
|
||||
color: #fff;
|
||||
|
||||
.warning {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,3 +27,7 @@
|
||||
font-weight: bold;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.warning {
|
||||
color: #ba3500;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {Component, Input, OnInit} from '@angular/core';
|
||||
import {Song} from '../../services/song';
|
||||
import {faBalanceScaleRight} from '@fortawesome/free-solid-svg-icons/faBalanceScaleRight';
|
||||
|
||||
@Component({
|
||||
selector: 'app-list-item',
|
||||
@@ -8,6 +9,7 @@ import {Song} from '../../services/song';
|
||||
})
|
||||
export class ListItemComponent implements OnInit {
|
||||
@Input() public song: Song;
|
||||
public faLegal = faBalanceScaleRight;
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<div>
|
||||
<app-list-header></app-list-header>
|
||||
<div *ngIf="songs$ | async as songs" [@fade]>
|
||||
<app-list-header [anyFilterActive]="anyFilterActive">
|
||||
<app-filter [songs]="songs" route="songs"></app-filter>
|
||||
</app-list-header>
|
||||
|
||||
<app-card *ngIf="songs$ | async as songs" [@fade] [padding]="false">
|
||||
<app-card [padding]="false">
|
||||
<app-list-item *ngFor="let song of songs" [routerLink]="song.id" [song]="song"></app-list-item>
|
||||
</app-card>
|
||||
</div>
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
.song-list {
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {combineLatest, Observable} from 'rxjs';
|
||||
import {fade} from '../../../animations';
|
||||
import {ActivatedRoute} from '@angular/router';
|
||||
import {filterSong} from '../../../services/filter.helper';
|
||||
import {FilterValues} from './filter/filter-values';
|
||||
|
||||
@Component({
|
||||
selector: 'app-songs',
|
||||
@@ -16,6 +17,7 @@ import {filterSong} from '../../../services/filter.helper';
|
||||
export class SongListComponent implements OnInit {
|
||||
|
||||
public songs$: Observable<Song[]>;
|
||||
public anyFilterActive = false;
|
||||
|
||||
constructor(private songService: SongService, private activatedRoute: ActivatedRoute) {
|
||||
}
|
||||
@@ -23,7 +25,7 @@ export class SongListComponent implements OnInit {
|
||||
ngOnInit() {
|
||||
const filter$ = this.activatedRoute.queryParams.pipe(
|
||||
debounceTime(300),
|
||||
map(_ => _.q)
|
||||
map(_ => _ as FilterValues)
|
||||
);
|
||||
|
||||
const songs$ = this.songService.list$().pipe(
|
||||
@@ -31,7 +33,35 @@ export class SongListComponent implements OnInit {
|
||||
);
|
||||
|
||||
this.songs$ = combineLatest([filter$, songs$]).pipe(
|
||||
map(_ => _[1].filter(song => filterSong(song, _[0])))
|
||||
map(_ => {
|
||||
let songs = _[1];
|
||||
let filter = _[0];
|
||||
this.anyFilterActive = this.checkIfFilterActive(filter);
|
||||
return songs.filter(song => this.filter(song, filter));
|
||||
})
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
private filter(song: Song, filter: FilterValues): boolean {
|
||||
let baseFilter = filterSong(song, filter.q);
|
||||
baseFilter = baseFilter && (!filter.type || filter.type === song.type);
|
||||
baseFilter = baseFilter && (!filter.legalType || filter.legalType === song.legalType);
|
||||
baseFilter = baseFilter && (!filter.flag || this.checkFlag(filter.flag, song.flags));
|
||||
|
||||
return baseFilter;
|
||||
}
|
||||
|
||||
private checkIfFilterActive(filter: FilterValues): boolean {
|
||||
return !!filter.q || !!filter.type || !!filter.legalType || !!filter.flag;
|
||||
}
|
||||
|
||||
private checkFlag(flag: string, flags: string) {
|
||||
if (!flags) return false;
|
||||
|
||||
const flagStrings = flags.split(';');
|
||||
if (flagStrings.length === 0) return false;
|
||||
|
||||
return flagStrings.indexOf(flag) !== -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,17 @@ import {CardModule} from '../../../widget-modules/components/card/card.module';
|
||||
import {RouterModule} from '@angular/router';
|
||||
import {LegalTypeTranslatorModule} from '../../../widget-modules/pipes/legal-type-translator/legal-type-translator.module';
|
||||
import {ListHeaderModule} from '../../../widget-modules/components/list-header/list-header.module';
|
||||
import {MatFormFieldModule} from '@angular/material/form-field';
|
||||
import {MatInputModule} from '@angular/material/input';
|
||||
import {FilterComponent} from './filter/filter.component';
|
||||
import {ReactiveFormsModule} from '@angular/forms';
|
||||
import {MatSelectModule} from '@angular/material/select';
|
||||
import {SongTypeTranslaterModule} from '../../../widget-modules/pipes/song-type-translater/song-type-translater.module';
|
||||
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [SongListComponent, ListItemComponent],
|
||||
declarations: [SongListComponent, ListItemComponent, FilterComponent],
|
||||
exports: [SongListComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
@@ -17,7 +24,13 @@ import {ListHeaderModule} from '../../../widget-modules/components/list-header/l
|
||||
|
||||
CardModule,
|
||||
LegalTypeTranslatorModule,
|
||||
ListHeaderModule
|
||||
ListHeaderModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
ReactiveFormsModule,
|
||||
MatSelectModule,
|
||||
SongTypeTranslaterModule,
|
||||
FontAwesomeModule
|
||||
]
|
||||
})
|
||||
export class SongListModule {
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-chip-list #chipList aria-label="Fruit selection">
|
||||
<mat-chip-list #chipList>
|
||||
<mat-chip (removed)="removeFlag(flag)" *ngFor="let flag of flags"
|
||||
[removable]="true" [selectable]="false">
|
||||
{{flag}}
|
||||
|
||||
@@ -16,10 +16,7 @@ export class FilterComponent {
|
||||
}
|
||||
|
||||
public async valueChange(text: string): Promise<void> {
|
||||
const route = text
|
||||
? this.router.createUrlTree(['songs'], {queryParams: {q: text}})
|
||||
: this.router.createUrlTree(['songs']);
|
||||
|
||||
const route = this.router.createUrlTree(['songs'], {queryParams: {q: text}, queryParamsHandling: 'merge'});
|
||||
await this.router.navigateByUrl(route);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
<div class="header">
|
||||
<button mat-icon-button>
|
||||
|
||||
<button (click)="onFilterClick()" [class.filter-active]="anyFilterActive" mat-icon-button>
|
||||
<fa-icon [icon]="faFilter"></fa-icon>
|
||||
</button>
|
||||
<button mat-icon-button routerLink="new">
|
||||
<fa-icon [icon]="faNew"></fa-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div *ngIf="filterVisible || anyFilterActive" @fade>
|
||||
<app-card>
|
||||
<ng-content></ng-content>
|
||||
</app-card>
|
||||
</div>
|
||||
|
||||
@@ -13,3 +13,8 @@
|
||||
color: #A6C4F5;
|
||||
|
||||
}
|
||||
|
||||
.filter-active {
|
||||
color: #a21;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
|
||||
import {faFilter} from '@fortawesome/free-solid-svg-icons/faFilter';
|
||||
import {faBars} from '@fortawesome/free-solid-svg-icons/faBars';
|
||||
import {faPlus} from '@fortawesome/free-solid-svg-icons/faPlus';
|
||||
import {fade} from '../../../animations';
|
||||
|
||||
@Component({
|
||||
selector: 'app-list-header',
|
||||
templateUrl: './list-header.component.html',
|
||||
styleUrls: ['./list-header.component.less']
|
||||
styleUrls: ['./list-header.component.less'],
|
||||
animations: [fade]
|
||||
})
|
||||
export class ListHeaderComponent implements OnInit {
|
||||
|
||||
public faNew = faPlus;
|
||||
public faFilter = faFilter;
|
||||
public faMenu = faBars;
|
||||
public filterVisible = false;
|
||||
@Output() filterVisibleChanged = new EventEmitter<boolean>();
|
||||
@Input() anyFilterActive = false;
|
||||
|
||||
constructor() {
|
||||
}
|
||||
@@ -20,4 +23,7 @@ export class ListHeaderComponent implements OnInit {
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
public onFilterClick(): void {
|
||||
this.filterVisible = !this.filterVisible || this.anyFilterActive;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import {ListHeaderComponent} from './list-header.component';
|
||||
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
|
||||
import {MatButtonModule} from '@angular/material/button';
|
||||
import {RouterModule} from '@angular/router';
|
||||
import {CardModule} from '../card/card.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [ListHeaderComponent],
|
||||
@@ -14,7 +15,8 @@ import {RouterModule} from '@angular/router';
|
||||
CommonModule,
|
||||
FontAwesomeModule,
|
||||
MatButtonModule,
|
||||
RouterModule
|
||||
RouterModule,
|
||||
CardModule,
|
||||
]
|
||||
})
|
||||
export class ListHeaderModule {
|
||||
|
||||
Reference in New Issue
Block a user