select filter
This commit is contained in:
parent
26eaa39411
commit
7a0ecf281a
@ -1,3 +1,17 @@
|
|||||||
|
<mat-toolbar style="background-color: red;">
|
||||||
|
<button mat-icon-button class="example-icon" aria-label="Example icon-button with menu icon">
|
||||||
|
<mat-icon>menu</mat-icon>
|
||||||
|
</button>
|
||||||
|
<span>My App</span>
|
||||||
|
<span class="example-spacer"></span>
|
||||||
|
<button mat-icon-button class="example-icon favorite-icon" aria-label="Example icon-button with heart icon">
|
||||||
|
<mat-icon>favorite</mat-icon>
|
||||||
|
</button>
|
||||||
|
<button mat-icon-button class="example-icon" aria-label="Example icon-button with share icon">
|
||||||
|
<mat-icon>share</mat-icon>
|
||||||
|
</button>
|
||||||
|
</mat-toolbar>
|
||||||
|
|
||||||
<app-progress-stepper
|
<app-progress-stepper
|
||||||
[steps]="['Bla', 'Noch ein Test', 'Abgeschlossen', '2', '4', '5', '2', '4', '5']"></app-progress-stepper>
|
[steps]="['Bla', 'Noch ein Test', 'Abgeschlossen', '2', '4', '5', '2', '4', '5']"></app-progress-stepper>
|
||||||
|
|
||||||
@ -23,4 +37,15 @@
|
|||||||
|
|
||||||
<div>Comment Value: {{parseComment(form)}}</div>
|
<div>Comment Value: {{parseComment(form)}}</div>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Choose an option</mat-label>
|
||||||
|
<mat-select multiple [formControl]="selectedItemsList">
|
||||||
|
<app-select-filter [array]="allOptions" (filteredReturn)="filteredOptions=$event" (selectAll)="selectAll()"
|
||||||
|
(unselectAll)="unselectAll()"></app-select-filter>
|
||||||
|
@for (item of filteredOptions; track $index) {
|
||||||
|
<mat-option [value]="item">{{item}}</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
<router-outlet />
|
<router-outlet />
|
@ -3,12 +3,21 @@ import { RouterOutlet } from '@angular/router';
|
|||||||
import { CommentBoxComponent } from "./comment-box/comment-box.component";
|
import { CommentBoxComponent } from "./comment-box/comment-box.component";
|
||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { ProgressStepperComponent } from "./progress-stepper/progress-stepper.component";
|
import { ProgressStepperComponent } from "./progress-stepper/progress-stepper.component";
|
||||||
|
import { Observable, startWith, map } from 'rxjs';
|
||||||
|
import { MatOptionModule } from '@angular/material/core';
|
||||||
|
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||||
|
import { MatChipsModule } from '@angular/material/chips';
|
||||||
|
import { AsyncPipe } from '@angular/common';
|
||||||
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
|
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { SelectFilterComponent } from "./select-filter/select-filter.component";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
imports: [RouterOutlet, CommentBoxComponent, MatFormFieldModule, MatInputModule, ReactiveFormsModule, ProgressStepperComponent],
|
imports: [RouterOutlet, MatToolbarModule, MatIconModule, CommentBoxComponent, MatFormFieldModule, MatInputModule, ReactiveFormsModule, ProgressStepperComponent, MatOptionModule, MatSelectModule, MatAutocompleteModule, MatChipsModule, AsyncPipe, FormsModule, SelectFilterComponent],
|
||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
styleUrl: './app.component.scss'
|
styleUrl: './app.component.scss'
|
||||||
})
|
})
|
||||||
@ -24,4 +33,16 @@ export class AppComponent {
|
|||||||
if (comment.value)
|
if (comment.value)
|
||||||
return comment.value
|
return comment.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
allOptions = ['Apple', 'Banana', 'Orange', 'Pear', 'Grape'];
|
||||||
|
filteredOptions = this.allOptions.slice();
|
||||||
|
selectedItemsList = new FormControl();
|
||||||
|
|
||||||
|
selectAll(): void {
|
||||||
|
this.selectedItemsList.setValue(this.allOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
unselectAll(): void {
|
||||||
|
this.selectedItemsList.setValue([]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
18
src/app/select-filter/select-filter.component.html
Normal file
18
src/app/select-filter/select-filter.component.html
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<form [formGroup]="searchForm" class="mat-filter">
|
||||||
|
<div>
|
||||||
|
<input #input class="mat-filter-input" matInput placeholder="Suchen..." formControlName="value"
|
||||||
|
(keydown)="handleKeydown($event)">
|
||||||
|
@if (localSpinner) {
|
||||||
|
<mat-spinner class="spinner" diameter="16"></mat-spinner>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
@if (noResults) {
|
||||||
|
<div class="noResultsMessage">
|
||||||
|
{{noResultsMessage}}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</form>
|
||||||
|
<div class="selection-buttons">
|
||||||
|
<div class="button" matRipple (click)="selectAll.emit()">Alle auswählen</div>
|
||||||
|
<div class="button" matRipple (click)="unselectAll.emit()">Alle abwählen</div>
|
||||||
|
</div>
|
69
src/app/select-filter/select-filter.component.scss
Normal file
69
src/app/select-filter/select-filter.component.scss
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
.mat-filter {
|
||||||
|
position: sticky;
|
||||||
|
top: -8px;
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
border-bottom-style: solid;
|
||||||
|
border-bottom-color: grey;
|
||||||
|
z-index: 100;
|
||||||
|
font-size: inherit;
|
||||||
|
box-shadow: none;
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 8px;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
background-color: var(--mat-sys-surface-container);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-filter-input {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
outline: none;
|
||||||
|
border: 0;
|
||||||
|
background-color: unset;
|
||||||
|
color: gray;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
position: absolute;
|
||||||
|
right: 16px;
|
||||||
|
top: calc(50% - 8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.noResultsMessage {
|
||||||
|
margin-top: 10px;
|
||||||
|
font-family: Roboto, "Helvetica Neue", sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selection-buttons {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 50% 50%;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
border-bottom-style: solid;
|
||||||
|
border-bottom-color: grey;
|
||||||
|
|
||||||
|
.button {
|
||||||
|
display: flex;
|
||||||
|
padding: 0.15rem;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
background-color: var(
|
||||||
|
--mat-option-hover-state-layer-color,
|
||||||
|
color-mix(
|
||||||
|
in srgb,
|
||||||
|
var(--mat-sys-on-surface) calc(var(--mat-sys-hover-state-layer-opacity) * 100%),
|
||||||
|
transparent
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
src/app/select-filter/select-filter.component.spec.ts
Normal file
23
src/app/select-filter/select-filter.component.spec.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { SelectFilterComponent } from './select-filter.component';
|
||||||
|
|
||||||
|
describe('SelectFilterComponent', () => {
|
||||||
|
let component: SelectFilterComponent;
|
||||||
|
let fixture: ComponentFixture<SelectFilterComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [SelectFilterComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(SelectFilterComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
85
src/app/select-filter/select-filter.component.ts
Normal file
85
src/app/select-filter/select-filter.component.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import { A, Z, ZERO, NINE, SPACE } from '@angular/cdk/keycodes';
|
||||||
|
import { Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core';
|
||||||
|
import { FormGroup, FormBuilder, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||||
|
import { MatRippleModule } from '@angular/material/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-select-filter',
|
||||||
|
imports: [ReactiveFormsModule, MatProgressSpinnerModule, MatRippleModule],
|
||||||
|
templateUrl: './select-filter.component.html',
|
||||||
|
styleUrl: './select-filter.component.scss'
|
||||||
|
})
|
||||||
|
export class SelectFilterComponent {
|
||||||
|
|
||||||
|
private searchFormValueChangesSubscription!: Subscription;
|
||||||
|
@ViewChild('input', { static: true }) input!: ElementRef;
|
||||||
|
|
||||||
|
@Input('array') array: any[] = [];
|
||||||
|
@Input('showSpinner') showSpinner = true;
|
||||||
|
@Input('noResultsMessage') noResultsMessage = 'No results';
|
||||||
|
|
||||||
|
noResults = false;
|
||||||
|
|
||||||
|
localSpinner = false;
|
||||||
|
@Output() filteredReturn = new EventEmitter<any>();
|
||||||
|
@Output() selectAll = new EventEmitter<void>();
|
||||||
|
@Output() unselectAll = new EventEmitter<void>();
|
||||||
|
|
||||||
|
public filteredItems: any = [];
|
||||||
|
public searchForm: FormGroup;
|
||||||
|
|
||||||
|
constructor(fb: FormBuilder) {
|
||||||
|
this.searchForm = fb.group({
|
||||||
|
value: ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.searchFormValueChangesSubscription = this.searchForm.valueChanges.subscribe(change => {
|
||||||
|
if (this.showSpinner) {
|
||||||
|
this.localSpinner = true;
|
||||||
|
}
|
||||||
|
if (change.value) {
|
||||||
|
// IF THE DISPLAY MEMBER INPUT IS SET WE CHECK THE SPECIFIC PROPERTY
|
||||||
|
|
||||||
|
this.filteredItems = this.array.filter((name: string) => name.toLowerCase().includes(change.value.toLowerCase()));
|
||||||
|
// OTHERWISE, WE CHECK THE ENTIRE STRING
|
||||||
|
|
||||||
|
// NO RESULTS VALIDATION
|
||||||
|
|
||||||
|
this.noResults = this.filteredItems == null || this.filteredItems.length === 0;
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this.filteredItems = this.array.slice();
|
||||||
|
this.noResults = false;
|
||||||
|
}
|
||||||
|
this.filteredReturn.emit(this.filteredItems);
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this.showSpinner) {
|
||||||
|
this.localSpinner = false;
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.input.nativeElement.focus();
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeydown(event: KeyboardEvent) {
|
||||||
|
// PREVENT PROPAGATION FOR ALL ALPHANUMERIC CHARACTERS IN ORDER TO AVOID SELECTION ISSUES
|
||||||
|
if ((event.key && event.key.length === 1) ||
|
||||||
|
(event.keyCode >= A && event.keyCode <= Z) ||
|
||||||
|
(event.keyCode >= ZERO && event.keyCode <= NINE) ||
|
||||||
|
(event.keyCode === SPACE)) {
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.filteredReturn.emit(this.array);
|
||||||
|
this.searchFormValueChangesSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user