-
Notifications
You must be signed in to change notification settings - Fork 27
Number range filter #2478
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Number range filter #2478
Changes from all commits
a35c570
211b46a
aa26a1b
ab412c9
a9730d9
c2d56d2
52002b2
55f4065
f1cb6e0
2a9b01a
f954f9a
bbd934d
e15d507
3298374
61f7484
d3edd88
7050631
ab14ff1
63de237
28f96e9
70a0449
fc926df
972ab4c
2a83602
955dd71
59d10c2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| import { applicationConfig, Meta, StoryObj } from "@storybook/angular"; | ||
| import { Entity } from "app/core/entity/model/entity"; | ||
| import { provideAnimations } from "@angular/platform-browser/animations"; | ||
| import { DateRangeFilterComponent } from "./date-range-filter.component"; | ||
| import { DateFilter } from "app/core/filter/filters/dateFilter"; | ||
| import { provideNativeDateAdapter } from "@angular/material/core"; | ||
|
|
||
| export default { | ||
| title: "Core/> App Layout/Filter/Date Range Filter", | ||
| component: DateRangeFilterComponent<Entity>, | ||
| decorators: [ | ||
| applicationConfig({ | ||
| providers: [provideAnimations(), provideNativeDateAdapter()], | ||
| }), | ||
| ], | ||
| } as Meta; | ||
|
|
||
| const filterConfig: DateFilter<Entity> = new DateFilter<Entity>( | ||
| "x", | ||
| "Demo Date Filter", | ||
| [], | ||
| ); | ||
|
|
||
| export const Default: StoryObj<DateRangeFilterComponent<Entity>> = { | ||
| args: { | ||
| filterConfig, | ||
| }, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| <mat-form-field> | ||
| <mat-label>{{ filterConfig.label || filterConfig.name }}</mat-label> | ||
| <app-range-input | ||
| [formControl]="formControl" | ||
| [activateValidation]="true" | ||
| ></app-range-input> | ||
| </mat-form-field> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| div { | ||
| display: flex; | ||
| } | ||
| input { | ||
| border: none; | ||
| background: none; | ||
| padding: 0; | ||
| outline: none; | ||
| font: inherit; | ||
| text-align: center; | ||
| color: currentColor; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import { ComponentFixture, TestBed } from "@angular/core/testing"; | ||
|
|
||
| import { NumberRangeFilterComponent } from "./number-range-filter.component"; | ||
| import { Entity } from "app/core/entity/model/entity"; | ||
| import { NumberFilter } from "app/core/filter/filters/numberFilter"; | ||
| import { NoopAnimationsModule } from "@angular/platform-browser/animations"; | ||
|
|
||
| describe("NumberRangeFilterComponent", () => { | ||
| let component: NumberRangeFilterComponent<Entity>; | ||
| let fixture: ComponentFixture<NumberRangeFilterComponent<Entity>>; | ||
|
|
||
| let filterConfig: NumberFilter<Entity>; | ||
|
|
||
| beforeEach(async () => { | ||
| filterConfig = new NumberFilter<Entity>("x", "Demo Number Filter"); | ||
|
|
||
| await TestBed.configureTestingModule({ | ||
| imports: [NumberRangeFilterComponent, NoopAnimationsModule], | ||
| }).compileComponents(); | ||
|
|
||
| fixture = TestBed.createComponent(NumberRangeFilterComponent); | ||
| component = fixture.componentInstance; | ||
|
|
||
| component.filterConfig = filterConfig; | ||
|
|
||
| fixture.detectChanges(); | ||
| }); | ||
|
|
||
| it("should create", () => { | ||
| expect(component).toBeTruthy(); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| import { Component, Input } from "@angular/core"; | ||
| import { Entity } from "../../../entity/model/entity"; | ||
| import { MatFormFieldModule } from "@angular/material/form-field"; | ||
| import { FormControl, ReactiveFormsModule } from "@angular/forms"; | ||
| import { | ||
| NumericRange, | ||
| RangeInputComponent, | ||
| } from "./range-input/range-input.component"; | ||
| import { NumberFilter } from "../../../filter/filters/numberFilter"; | ||
|
|
||
| @Component({ | ||
| selector: "app-date-range-filter", | ||
| templateUrl: "./number-range-filter.component.html", | ||
| styleUrls: ["./number-range-filter.component.scss"], | ||
| standalone: true, | ||
| imports: [MatFormFieldModule, ReactiveFormsModule, RangeInputComponent], | ||
| }) | ||
| export class NumberRangeFilterComponent<T extends Entity> { | ||
| @Input() filterConfig: NumberFilter<T>; | ||
|
|
||
| formControl: FormControl<NumericRange>; | ||
| from: number; | ||
| to: number; | ||
|
|
||
| ngOnInit() { | ||
| this.formControl = new FormControl<NumericRange>({ | ||
| from: Number(this.filterConfig.selectedOptionValues[0]), | ||
| to: Number(this.filterConfig.selectedOptionValues[1]), | ||
| }); | ||
| this.formControl.valueChanges.subscribe((value) => { | ||
| this.filterConfig.selectedOptionValues = [ | ||
| this.formControl.value.from?.toString() ?? "", | ||
| this.formControl.value.to?.toString() ?? "", | ||
| ]; | ||
|
|
||
| this.filterConfig.selectedOptionChange.emit( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we could add a unit test to ensure a formControl change correctly updates the filterConfig (and emits selectedOptionChange) |
||
| this.filterConfig.selectedOptionValues, | ||
| ); | ||
| }); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| import { applicationConfig, Meta, StoryObj } from "@storybook/angular"; | ||
| import { NumberRangeFilterComponent } from "./number-range-filter.component"; | ||
| import { Entity } from "app/core/entity/model/entity"; | ||
| import { NumberFilter } from "app/core/filter/filters/numberFilter"; | ||
| import { provideAnimations } from "@angular/platform-browser/animations"; | ||
|
|
||
| export default { | ||
| title: "Core/> App Layout/Filter/Number Range Filter", | ||
| component: NumberRangeFilterComponent<Entity>, | ||
| decorators: [ | ||
| applicationConfig({ | ||
| providers: [provideAnimations()], | ||
| }), | ||
| ], | ||
| } as Meta; | ||
|
|
||
| const filterConfig: NumberFilter<Entity> = new NumberFilter<Entity>( | ||
| "numberFilter", | ||
| ); | ||
| filterConfig.label = "Demo Number Filter"; | ||
|
|
||
| export const Default: StoryObj<NumberRangeFilterComponent<Entity>> = { | ||
| args: { | ||
| filterConfig, | ||
| }, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| <div [formGroup]="formGroup" class="container"> | ||
| <input formControlName="from" type="number" class="input-element" /> | ||
| <span>–</span> | ||
| <input formControlName="to" type="number" class="input-element" /> | ||
| </div> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| .container { | ||
| display: flex; | ||
| } | ||
|
|
||
| .input-element { | ||
| border: none; | ||
| background: none; | ||
| padding: 0; | ||
| outline: none; | ||
| font: inherit; | ||
| text-align: center; | ||
| color: currentColor; | ||
|
|
||
| max-width: calc(50% - 10px); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import { ComponentFixture, TestBed } from "@angular/core/testing"; | ||
|
|
||
| import { RangeInputComponent } from "./range-input.component"; | ||
|
|
||
| describe("RangeInputComponent", () => { | ||
| let component: RangeInputComponent; | ||
| let fixture: ComponentFixture<RangeInputComponent>; | ||
|
|
||
| beforeEach(async () => { | ||
| await TestBed.configureTestingModule({ | ||
| imports: [RangeInputComponent], | ||
| }).compileComponents(); | ||
|
|
||
| fixture = TestBed.createComponent(RangeInputComponent); | ||
| component = fixture.componentInstance; | ||
| fixture.detectChanges(); | ||
| }); | ||
|
|
||
| it("should create", () => { | ||
| expect(component).toBeTruthy(); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| import { Component, ElementRef, Input, Optional, Self } from "@angular/core"; | ||
| import { | ||
| FormControl, | ||
| FormControlDirective, | ||
| FormGroup, | ||
| FormGroupDirective, | ||
| NgControl, | ||
| NgForm, | ||
| ReactiveFormsModule, | ||
| ValidationErrors, | ||
| ValidatorFn, | ||
| } from "@angular/forms"; | ||
| import { ErrorStateMatcher } from "@angular/material/core"; | ||
| import { MatFormFieldControl } from "@angular/material/form-field"; | ||
| import { CustomFormControlDirective } from "app/core/common-components/basic-autocomplete/custom-form-control.directive"; | ||
| import { MatInput } from "@angular/material/input"; | ||
|
|
||
| @Component({ | ||
| selector: "app-range-input", | ||
| standalone: true, | ||
| imports: [MatInput, ReactiveFormsModule], | ||
| templateUrl: "./range-input.component.html", | ||
| styleUrl: "./range-input.component.scss", | ||
| providers: [ | ||
| { provide: MatFormFieldControl, useExisting: RangeInputComponent }, | ||
| ], | ||
| }) | ||
| export class RangeInputComponent extends CustomFormControlDirective<NumericRange> { | ||
| formGroup: FormGroup = new FormGroup({ | ||
| from: new FormControl(), | ||
| to: new FormControl(), | ||
| }); | ||
|
|
||
| @Input() override set value(value: NumericRange) { | ||
| // update the internal formGroup when the value changes from the outside | ||
| this.formGroup.setValue(value, { emitEvent: false }); | ||
| super.value = value; | ||
| } | ||
| override get value(): NumericRange { | ||
| return super.value; | ||
| } | ||
|
|
||
| /** | ||
| * Validation (activated by default) ensures the component has a sensible range | ||
| * (e.g. "from" <= "to") | ||
| * or otherwise marks the formControl invalid | ||
| */ | ||
| @Input() activateValidation: boolean = true; | ||
|
|
||
| constructor( | ||
| elementRef: ElementRef<HTMLElement>, | ||
| errorStateMatcher: ErrorStateMatcher, | ||
| @Optional() @Self() ngControl: NgControl, | ||
| @Optional() parentForm: NgForm, | ||
| @Optional() parentFormGroup: FormGroupDirective, | ||
| @Optional() private formControlDirective: FormControlDirective, | ||
| ) { | ||
| super( | ||
| elementRef, | ||
| errorStateMatcher, | ||
| ngControl, | ||
| parentForm, | ||
| parentFormGroup, | ||
| ); | ||
|
|
||
| this.formGroup.valueChanges.subscribe((value) => { | ||
| this.value = value; | ||
| }); | ||
| } | ||
|
|
||
| private validatorFunction: ValidatorFn = (): ValidationErrors | null => { | ||
| if ( | ||
| this.value.from != undefined && | ||
| this.value.to != undefined && | ||
| this.value.from > this.value.to | ||
| ) { | ||
| return { | ||
| fromGreaterThanTo: "The 'from' value is greater than the 'to' value.", | ||
| }; | ||
| } else { | ||
| return null; | ||
| } | ||
| }; | ||
|
|
||
| ngAfterViewInit() { | ||
| if (this.activateValidation) { | ||
| this.formControlDirective.form.addValidators([this.validatorFunction]); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we could add a unit test for this |
||
| } | ||
| } | ||
| } | ||
|
|
||
| export class NumericRange { | ||
| constructor( | ||
| public from: number, | ||
| public to: number, | ||
| ) {} | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.