Skip to content

Commit

Permalink
feat(stark-ui): add directive for transforming input value before pas…
Browse files Browse the repository at this point in the history
…sing it to a ngControl

  - added directive `[starkTransformInput]`
  - added partial demo
  - small refactor demo page
  - added test for directive

ISSUES CLOSED: NationalBankBelgium#1099
  • Loading branch information
carlo-nomes committed Feb 1, 2019
1 parent 7ebcd5a commit 4393233
Show file tree
Hide file tree
Showing 11 changed files with 236 additions and 62 deletions.
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./directives/on-enter-key.directive";
export * from "./directives/restrict-input.directive";
export * from "./directives/transform-input.directive";
Original file line number Diff line number Diff line change
@@ -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: "<input [(ngModel)]='value' [starkTransformInput]='transformation'/>"
})
class TestComponent {
public value: string = "";
public transformation = (value: string) => value.toUpperCase();
}

let fixture: ComponentFixture<TestComponent>;

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 = (<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'");
});

});
Original file line number Diff line number Diff line change
@@ -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);
}
}

}
Original file line number Diff line number Diff line change
@@ -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 {}
3 changes: 2 additions & 1 deletion showcase/src/app/demo-ui/demo-ui.module.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -77,6 +77,7 @@ import {
}),
CommonModule,
FormsModule,
ReactiveFormsModule,
MatButtonModule,
MatButtonToggleModule,
MatCardModule,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,44 @@ <h1 class="mat-display-3" translate>SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.TITLE</h1>
<section class="stark-section">
<h1 translate>SHOWCASE.DEMO.SHARED.EXAMPLE_VIEWER_LIST</h1>
<example-viewer
[extensions]="['HTML', 'TS']"
filesPath="keyboard-directives/on-enter-key-directive"
exampleTitle="SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.ON_ENTER_KEY.TITLE"
[extensions]="['HTML', 'TS']"
filesPath="keyboard-directives/on-enter-key-directive"
exampleTitle="SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.ON_ENTER_KEY.TITLE"
>
<form class="on-enter-key-directive-form">
<form fxLayout="column">
<p translate>SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.ON_ENTER_KEY.DESCRIPTION</p>
<mat-form-field>
<mat-label translate>SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.ON_ENTER_KEY.INPUT_WITH_CONTEXT</mat-label>
<input
name="test"
[(ngModel)]="inputValue1"
matInput
placeholder="{{ 'SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.ON_ENTER_KEY.TYPE_AND_PRESS_ENTER' | translate }}"
[starkOnEnterKey]="onEnterKeyCallback.bind(this)"
[starkOnEnterKeyParams]="['input1', inputValue1, 123, { prop: 'someValue' }]"
name="test"
[(ngModel)]="inputValue1"
matInput
placeholder="{{ 'SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.ON_ENTER_KEY.TYPE_AND_PRESS_ENTER' | translate }}"
[starkOnEnterKey]="onEnterKeyCallback.bind(this)"
[starkOnEnterKeyParams]="['input1', inputValue1, 123, { prop: 'someValue' }]"
/>
</mat-form-field>
<mat-form-field>
<mat-label translate>SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.ON_ENTER_KEY.INPUT_WITHOUT_CONTEXT</mat-label>
<input
name="test"
[(ngModel)]="inputValue2"
matInput
placeholder="{{ 'SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.ON_ENTER_KEY.TYPE_PRESS_ENTER_AND_CHECK_CONSOLE' | translate }}"
[starkOnEnterKey]="onEnterKeyCallback"
[starkOnEnterKeyParams]="['input2', inputValue2, 123, { prop: 'someValue' }]"
name="test"
[(ngModel)]="inputValue2"
matInput
placeholder="{{ 'SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.ON_ENTER_KEY.TYPE_PRESS_ENTER_AND_CHECK_CONSOLE' | translate }}"
[starkOnEnterKey]="onEnterKeyCallback"
[starkOnEnterKeyParams]="['input2', inputValue2, 123, { prop: 'someValue' }]"
/>
</mat-form-field>
<mat-form-field>
<mat-label translate>SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.ON_ENTER_KEY.INPUT_WITHOUT_CONTEXT_ALTERNATIVE </mat-label>
<mat-label translate>SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.ON_ENTER_KEY.INPUT_WITHOUT_CONTEXT_ALTERNATIVE
</mat-label>
<input
name="test"
[(ngModel)]="inputValue3"
matInput
placeholder="{{ 'SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.ON_ENTER_KEY.TYPE_AND_PRESS_ENTER' | translate }}"
[starkOnEnterKey]="onEnterKeyCallback"
[starkOnEnterKeyParams]="['input3', inputValue3, 123, { prop: 'someValue' }, this]"
name="test"
[(ngModel)]="inputValue3"
matInput
placeholder="{{ 'SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.ON_ENTER_KEY.TYPE_AND_PRESS_ENTER' | translate }}"
[starkOnEnterKey]="onEnterKeyCallback"
[starkOnEnterKeyParams]="['input3', inputValue3, 123, { prop: 'someValue' }, this]"
/>
</mat-form-field>
</form>
Expand All @@ -47,49 +48,66 @@ <h1 translate>SHOWCASE.DEMO.SHARED.EXAMPLE_VIEWER_LIST</h1>
</example-viewer>

<example-viewer
[extensions]="['HTML', 'TS']"
filesPath="keyboard-directives/restrict-input-directive"
exampleTitle="SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.RESTRICT_INPUT.TITLE"
[extensions]="['HTML', 'TS']"
filesPath="keyboard-directives/restrict-input-directive"
exampleTitle="SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.RESTRICT_INPUT.TITLE"
>
<form class="restrict-input-directive-form">
<form fxLayout="column">
<p translate>SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.RESTRICT_INPUT.DESCRIPTION</p>
<mat-form-field>
<mat-label translate>SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.RESTRICT_INPUT.INPUT_ONLY_NUMBERS</mat-label>
<input
name="test"
matInput
placeholder="{{ 'SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.RESTRICT_INPUT.TYPE_A_VALUE' | translate }}"
starkRestrictInput="\d"
name="test"
matInput
placeholder="{{ 'SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.RESTRICT_INPUT.TYPE_A_VALUE' | translate }}"
starkRestrictInput="\d"
/>
</mat-form-field>
<mat-form-field>
<mat-label translate>SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.RESTRICT_INPUT.INPUT_ALPHANUMERICAL_CHARACTERS </mat-label>
<mat-label translate>SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.RESTRICT_INPUT.INPUT_ALPHANUMERICAL_CHARACTERS
</mat-label>
<input
name="test"
matInput
placeholder="{{ 'SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.RESTRICT_INPUT.TYPE_A_VALUE' | translate }}"
starkRestrictInput="^[A-Za-z0-9]*$"
name="test"
matInput
placeholder="{{ 'SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.RESTRICT_INPUT.TYPE_A_VALUE' | translate }}"
starkRestrictInput="^[A-Za-z0-9]*$"
/>
</mat-form-field>
<mat-form-field>
<mat-label translate>SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.RESTRICT_INPUT.INPUT_NO_SPECIAL_CHARACTERS </mat-label>
<mat-label translate>SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.RESTRICT_INPUT.INPUT_NO_SPECIAL_CHARACTERS
</mat-label>
<input
name="test"
matInput
placeholder="{{ 'SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.RESTRICT_INPUT.TYPE_A_VALUE' | translate }}"
starkRestrictInput="\w"
name="test"
matInput
placeholder="{{ 'SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.RESTRICT_INPUT.TYPE_A_VALUE' | translate }}"
starkRestrictInput="\w"
/>
</mat-form-field>
<mat-form-field>
<mat-label translate>SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.RESTRICT_INPUT.INPUT_UPPERCASE_CHARACTERS</mat-label>
<mat-label translate>SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.RESTRICT_INPUT.INPUT_UPPERCASE_CHARACTERS
</mat-label>
<input
name="test"
matInput
placeholder="{{ 'SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.RESTRICT_INPUT.TYPE_A_VALUE' | translate }}"
starkRestrictInput="^[A-Z]*$"
name="test"
matInput
placeholder="{{ 'SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.RESTRICT_INPUT.TYPE_A_VALUE' | translate }}"
starkRestrictInput="^[A-Z]*$"
/>
</mat-form-field>
</form>
</example-viewer>

<example-viewer [extensions]="['HTML', 'TS']"
filesPath="keyboard-directives/restrict-input-directive"
exampleTitle="SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.TRANSFORM_INPUT.TITLE">
<div fxlayout="column">
<mat-form-field fxFlex>
<mat-label translate>SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.TRANSFORM_INPUT.ONLY_UPPER_CASE_LABEL</mat-label>
<input matInput
placeholder="{{ 'SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.TRANSFORM_INPUT.ONLY_UPPER_CASE_PLACEHOLDER' | translate }}"
[formControl]="transformFormControl"
[starkTransformInput]="transformFunction"/>
</mat-form-field>
</div>
</example-viewer>
</section>
<stark-reference-block [links]="referenceList"></stark-reference-block>
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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
Expand All @@ -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 {
Expand Down
5 changes: 5 additions & 0 deletions showcase/src/assets/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
Loading

0 comments on commit 4393233

Please sign in to comment.