diff --git a/packages/stark-ui/src/modules/keyboard-directives/directives.ts b/packages/stark-ui/src/modules/keyboard-directives/directives.ts index 3cbc7b907c..05c3f63118 100644 --- a/packages/stark-ui/src/modules/keyboard-directives/directives.ts +++ b/packages/stark-ui/src/modules/keyboard-directives/directives.ts @@ -1,2 +1,3 @@ export * from "./directives/on-enter-key.directive"; export * from "./directives/restrict-input.directive"; +export * from "./directives/transform-input.directive"; diff --git a/packages/stark-ui/src/modules/keyboard-directives/directives/transform-input.directive.spec.ts b/packages/stark-ui/src/modules/keyboard-directives/directives/transform-input.directive.spec.ts new file mode 100644 index 0000000000..98e20e5adc --- /dev/null +++ b/packages/stark-ui/src/modules/keyboard-directives/directives/transform-input.directive.spec.ts @@ -0,0 +1,51 @@ +import { Component, DebugElement } from "@angular/core"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { FormsModule, ReactiveFormsModule } from "@angular/forms"; +import { By } from "@angular/platform-browser"; +import { StarkTransformInputDirective } from "@nationalbankbelgium/stark-ui"; +import { MockStarkLoggingService } from "@nationalbankbelgium/stark-core/testing"; +import { STARK_LOGGING_SERVICE } from "@nationalbankbelgium/stark-core"; + +fdescribe("TransformsInputDirective with ngModel", () => { + @Component({ + selector: "test-component", + template: "" + }) + class TestComponent { + public value: string = ""; + public transformation = (value: string) => value.toUpperCase(); + } + + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [StarkTransformInputDirective, TestComponent], + imports: [ + FormsModule, + ReactiveFormsModule + ], + providers: [ + { provide: STARK_LOGGING_SERVICE, useValue: new MockStarkLoggingService() } + ] + }); + + fixture = TestBed.createComponent(TestComponent); + // trigger initial data binding + fixture.detectChanges(); + }); + + it("should set the value to uppercase", () => { + expect(fixture.componentInstance.value).toBe("", "Field 'value' should start as empty string "); + + // Mock input event + const debugInputElement: DebugElement = fixture.debugElement.query(By.css("input")); + const inputElement: HTMLInputElement = (debugInputElement.nativeElement); + inputElement.value = "a"; + inputElement.dispatchEvent(new Event("input")); + fixture.detectChanges(); + + expect(fixture.componentInstance.value).toBe("A", "Field 'value' should have been updated to upper case 'A'"); + }); + +}); diff --git a/packages/stark-ui/src/modules/keyboard-directives/directives/transform-input.directive.ts b/packages/stark-ui/src/modules/keyboard-directives/directives/transform-input.directive.ts new file mode 100644 index 0000000000..cb9bff413f --- /dev/null +++ b/packages/stark-ui/src/modules/keyboard-directives/directives/transform-input.directive.ts @@ -0,0 +1,93 @@ +import { Directive, ElementRef, forwardRef, Input, Renderer2 } from "@angular/core"; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms"; + +export const STARK_TRANSFORM_INPUT_PROVIDER: any = { + provide: NG_VALUE_ACCESSOR, + // tslint:disable-next-line:no-forward-ref + useExisting: forwardRef(() => StarkTransformInputDirective), + multi: true +}; + +/** + * Directive to transform the value of an input / textarea before it is passed on to the ngControl (ngModel, formControl). + * This is mostly based on {@link: @angular/forms/DefaultValueAccessor} + */ +@Directive({ + // tslint:disable-next-line:directive-selector + selector: "[starkTransformInput]", + providers: [STARK_TRANSFORM_INPUT_PROVIDER], + host: { + "(input)": "$any(this)._handleInput($event.target.value)", + "(blur)": "onTouched()" + } +}) +export class StarkTransformInputDirective implements ControlValueAccessor { + // tslint:disable-next-line:no-input-rename + @Input("starkTransformInput") + public transformFunction: (value: any) => any; + + /** + * The registered callback function called when an input event occurs on the input element. + */ + public onChange = (_: any) => {/*noop*/}; + + /** + * The registered callback function called when a blur event occurs on the input element. + */ + public onTouched = () => {/*noop*/}; + + /** + * Class constructor + * @param logger - The logger of the application + * @param _renderer - Angular renderer + * @param _elementRef - Reference to the element + */ + public constructor(private _renderer: Renderer2, + private _elementRef: ElementRef) { + } + + /** + * Sets the "value" property on the input element. + * + * @param value The checked value + */ + public writeValue(value: any): void { + const normalizedValue: any = value === null ? "" : value; + this._renderer.setProperty(this._elementRef.nativeElement, "value", normalizedValue); + } + + /** + * Registers a function called when the control value changes. + * + * @param fn The callback function + */ + public registerOnChange(fn: (_: any) => void): void { this.onChange = fn; } + + /** + * Registers a function called when the control is touched. + * + * @param fn The callback function + */ + public registerOnTouched(fn: () => void): void { this.onTouched = fn; } + + /** + * Sets the "disabled" property on the input element. + * + * @param isDisabled The disabled value + */ + public setDisabledState(isDisabled: boolean): void { + this._renderer.setProperty(this._elementRef.nativeElement, "disabled", isDisabled); + } + + /** @internal */ + public _handleInput(value: any): void { + const transformed: any = this.transformFunction(value); + if (transformed !== value) { + this._elementRef.nativeElement.value = transformed; + this.onChange(transformed); + } else { + this.onChange(value); + } + } + +} diff --git a/packages/stark-ui/src/modules/keyboard-directives/keyboard-directives.module.ts b/packages/stark-ui/src/modules/keyboard-directives/keyboard-directives.module.ts index 007412c877..b7c7511a92 100644 --- a/packages/stark-ui/src/modules/keyboard-directives/keyboard-directives.module.ts +++ b/packages/stark-ui/src/modules/keyboard-directives/keyboard-directives.module.ts @@ -1,8 +1,8 @@ import { NgModule } from "@angular/core"; -import { StarkOnEnterKeyDirective, StarkRestrictInputDirective } from "./directives"; +import { StarkTransformInputDirective, StarkOnEnterKeyDirective, StarkRestrictInputDirective } from "./directives"; @NgModule({ - declarations: [StarkOnEnterKeyDirective, StarkRestrictInputDirective], - exports: [StarkOnEnterKeyDirective, StarkRestrictInputDirective] + declarations: [StarkOnEnterKeyDirective, StarkRestrictInputDirective, StarkTransformInputDirective], + exports: [StarkOnEnterKeyDirective, StarkRestrictInputDirective, StarkTransformInputDirective] }) export class StarkKeyboardDirectivesModule {} diff --git a/showcase/src/app/demo-ui/demo-ui.module.ts b/showcase/src/app/demo-ui/demo-ui.module.ts index 27ca15a9e9..058451dd68 100644 --- a/showcase/src/app/demo-ui/demo-ui.module.ts +++ b/showcase/src/app/demo-ui/demo-ui.module.ts @@ -1,6 +1,6 @@ import { NgModule } from "@angular/core"; import { CommonModule } from "@angular/common"; -import { FormsModule } from "@angular/forms"; +import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { MAT_DATE_FORMATS } from "@angular/material/core"; import { MatCheckboxModule } from "@angular/material/checkbox"; import { MatButtonModule } from "@angular/material/button"; @@ -77,6 +77,7 @@ import { }), CommonModule, FormsModule, + ReactiveFormsModule, MatButtonModule, MatButtonToggleModule, MatCardModule, diff --git a/showcase/src/app/demo-ui/pages/keyboard-directives/demo-keyboard-directives-page.component.html b/showcase/src/app/demo-ui/pages/keyboard-directives/demo-keyboard-directives-page.component.html index f30a8ac282..c646ba5840 100644 --- a/showcase/src/app/demo-ui/pages/keyboard-directives/demo-keyboard-directives-page.component.html +++ b/showcase/src/app/demo-ui/pages/keyboard-directives/demo-keyboard-directives-page.component.html @@ -2,43 +2,44 @@ SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.TITLE SHOWCASE.DEMO.SHARED.EXAMPLE_VIEWER_LIST - + SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.ON_ENTER_KEY.DESCRIPTION SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.ON_ENTER_KEY.INPUT_WITH_CONTEXT SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.ON_ENTER_KEY.INPUT_WITHOUT_CONTEXT - SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.ON_ENTER_KEY.INPUT_WITHOUT_CONTEXT_ALTERNATIVE + SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.ON_ENTER_KEY.INPUT_WITHOUT_CONTEXT_ALTERNATIVE + @@ -47,49 +48,66 @@ SHOWCASE.DEMO.SHARED.EXAMPLE_VIEWER_LIST - + SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.RESTRICT_INPUT.DESCRIPTION SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.RESTRICT_INPUT.INPUT_ONLY_NUMBERS - SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.RESTRICT_INPUT.INPUT_ALPHANUMERICAL_CHARACTERS + SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.RESTRICT_INPUT.INPUT_ALPHANUMERICAL_CHARACTERS + - SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.RESTRICT_INPUT.INPUT_NO_SPECIAL_CHARACTERS + SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.RESTRICT_INPUT.INPUT_NO_SPECIAL_CHARACTERS + - SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.RESTRICT_INPUT.INPUT_UPPERCASE_CHARACTERS + SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.RESTRICT_INPUT.INPUT_UPPERCASE_CHARACTERS + + + + + + SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.TRANSFORM_INPUT.ONLY_UPPER_CASE_LABEL + + + + diff --git a/showcase/src/app/demo-ui/pages/keyboard-directives/demo-keyboard-directives-page.component.scss b/showcase/src/app/demo-ui/pages/keyboard-directives/demo-keyboard-directives-page.component.scss index 9bed527bce..4035ed81d1 100644 --- a/showcase/src/app/demo-ui/pages/keyboard-directives/demo-keyboard-directives-page.component.scss +++ b/showcase/src/app/demo-ui/pages/keyboard-directives/demo-keyboard-directives-page.component.scss @@ -1,13 +1,3 @@ -.on-enter-key-directive-form { - display: flex; - flex-direction: column; -} - -.restrict-input-directive-form { - display: flex; - flex-direction: column; -} - pre code { display: block; height: 200px; diff --git a/showcase/src/app/demo-ui/pages/keyboard-directives/demo-keyboard-directives-page.component.ts b/showcase/src/app/demo-ui/pages/keyboard-directives/demo-keyboard-directives-page.component.ts index 5752803add..79f95eca6d 100644 --- a/showcase/src/app/demo-ui/pages/keyboard-directives/demo-keyboard-directives-page.component.ts +++ b/showcase/src/app/demo-ui/pages/keyboard-directives/demo-keyboard-directives-page.component.ts @@ -1,6 +1,7 @@ import { Component, Inject, OnInit } from "@angular/core"; import { STARK_LOGGING_SERVICE, StarkLoggingService } from "@nationalbankbelgium/stark-core"; import { ReferenceLink } from "../../../shared/components"; +import { FormControl } from "@angular/forms"; @Component({ selector: "demo-keyboard-directives", @@ -12,12 +13,14 @@ export class DemoKeyboardDirectivesPageComponent implements OnInit { public inputValue1: string; public inputValue2: string; public inputValue3: string; - + public transformFormControl: FormControl = new FormControl("", [], []); public logging: string; public referenceList: ReferenceLink[]; + public transformFunction = (value: string): string => value.toUpperCase(); - public constructor(@Inject(STARK_LOGGING_SERVICE) private logger: StarkLoggingService) {} + public constructor(@Inject(STARK_LOGGING_SERVICE) private logger: StarkLoggingService) { + } /** * Component lifecycle hook @@ -39,6 +42,8 @@ export class DemoKeyboardDirectivesPageComponent implements OnInit { url: "https://stark.nbb.be/api-docs/stark-ui/latest/directives/StarkRestrictInputDirective.html" } ]; + + this.transformFormControl.valueChanges.subscribe((v: string) => this.logger.debug("transformFormControl value changed: ", v)); } public onEnterKeyCallback(...paramValues: any[]): void { diff --git a/showcase/src/assets/translations/en.json b/showcase/src/assets/translations/en.json index a78b891230..bd77df7e4f 100644 --- a/showcase/src/assets/translations/en.json +++ b/showcase/src/assets/translations/en.json @@ -101,6 +101,11 @@ "INPUT_UPPERCASE_CHARACTERS": "Accept only uppercase characters", "TYPE_A_VALUE": "Type a value" }, + "TRANSFORM_INPUT": { + "TITLE": "Transform Input Directive", + "ONLY_UPPER_CASE_LABEL": "Upper case input", + "ONLY_UPPER_CASE_PLACEHOLDER": "ONLY UPPER CASE" + }, "TITLE": "Keyboard directive" }, "LANGUAGE_SELECTOR": { diff --git a/showcase/src/assets/translations/fr.json b/showcase/src/assets/translations/fr.json index a319036271..2b224e8d3e 100644 --- a/showcase/src/assets/translations/fr.json +++ b/showcase/src/assets/translations/fr.json @@ -101,6 +101,11 @@ "INPUT_UPPERCASE_CHARACTERS": "Accepter uniquement les majuscules", "TYPE_A_VALUE": "Taper une valeur" }, + "TRANSFORM_INPUT": { + "TITLE": "Transform Input Directive", + "ONLY_UPPER_CASE_LABEL": "Upper case input", + "ONLY_UPPER_CASE_PLACEHOLDER": "ONLY UPPER CASE" + }, "TITLE": "Keyboard directive" }, "LANGUAGE_SELECTOR": { diff --git a/showcase/src/assets/translations/nl.json b/showcase/src/assets/translations/nl.json index 0fd7abffd1..52b65bff28 100644 --- a/showcase/src/assets/translations/nl.json +++ b/showcase/src/assets/translations/nl.json @@ -101,6 +101,11 @@ "INPUT_UPPERCASE_CHARACTERS": "Aanvaard enkel hoofdletters", "TYPE_A_VALUE": "Geef een waarde in" }, + "TRANSFORM_INPUT": { + "TITLE": "Transform Input Directive", + "ONLY_UPPER_CASE_LABEL": "Upper case input", + "ONLY_UPPER_CASE_PLACEHOLDER": "ONLY UPPER CASE" + }, "TITLE": "Keyboard directive" }, "LANGUAGE_SELECTOR": {
SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.ON_ENTER_KEY.DESCRIPTION
SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.RESTRICT_INPUT.DESCRIPTION