diff --git a/packages/stark-ui/src/modules/keyboard-directives/directives.ts b/packages/stark-ui/src/modules/keyboard-directives/directives.ts index 77eb0569da..3cbc7b907c 100644 --- a/packages/stark-ui/src/modules/keyboard-directives/directives.ts +++ b/packages/stark-ui/src/modules/keyboard-directives/directives.ts @@ -1 +1,2 @@ export * from "./directives/on-enter-key.directive"; +export * from "./directives/restrict-input.directive"; diff --git a/packages/stark-ui/src/modules/keyboard-directives/directives/restrict-input.directive.spec.ts b/packages/stark-ui/src/modules/keyboard-directives/directives/restrict-input.directive.spec.ts new file mode 100644 index 0000000000..4c8701bc77 --- /dev/null +++ b/packages/stark-ui/src/modules/keyboard-directives/directives/restrict-input.directive.spec.ts @@ -0,0 +1,122 @@ +/*tslint:disable:completed-docs*/ +import { ComponentFixture, fakeAsync, TestBed } from "@angular/core/testing"; +import { Component, DebugElement } from "@angular/core"; +import { By } from "@angular/platform-browser"; +import { STARK_LOGGING_SERVICE } from "@nationalbankbelgium/stark-core"; +import { MockStarkLoggingService } from "@nationalbankbelgium/stark-core/testing"; +import { StarkRestrictInputDirective } from "./restrict-input.directive"; +import Spy = jasmine.Spy; +import createSpy = jasmine.createSpy; + +describe("RestrictInputDirective", () => { + @Component({ + selector: "test-component", + template: getTemplate("starkRestrictInput") + }) + class TestComponent { + public onEnterKeyHandler: Spy = createSpy("onEnterKeyHandlerSpy"); + } + + let fixture: ComponentFixture; + + function getTemplate(restrictInputDirective: string): string { + return ""; + } + + function initializeComponentFixture(): void { + fixture = TestBed.createComponent(TestComponent); + // trigger initial data binding + fixture.detectChanges(); + } + + function triggerKeyPressEvent(inputElement: DebugElement, value: string): KeyboardEvent { + (inputElement.nativeElement).value = value; + + const keypressEvent: Event = document.createEvent("Event"); + keypressEvent.initEvent("keypress", true, true); + keypressEvent["char"] = value; + keypressEvent["charCode"] = value.charCodeAt(0); + inputElement.triggerEventHandler("keypress", keypressEvent); + + return keypressEvent; + } + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [StarkRestrictInputDirective, TestComponent], + providers: [{ provide: STARK_LOGGING_SERVICE, useValue: new MockStarkLoggingService() }] + }); + }); + + describe("when input restriction is not defined", () => { + beforeEach( + fakeAsync(() => { + // compile template and css + return TestBed.compileComponents(); + }) + ); + + beforeEach(() => { + initializeComponentFixture(); + }); + + it("should NOT prevent any value from being typed in the input when no input restriction was provided", () => { + expect(fixture).toBeDefined(); + const inputElement: DebugElement = fixture.debugElement.query(By.css("input")); + + let keyPressEvent: Event = triggerKeyPressEvent(inputElement, "1"); + expect(keyPressEvent.defaultPrevented).toBe(false); + + keyPressEvent = triggerKeyPressEvent(inputElement, "9"); + expect(keyPressEvent.defaultPrevented).toBe(false); + + keyPressEvent = triggerKeyPressEvent(inputElement, "0"); + expect(keyPressEvent.defaultPrevented).toBe(false); + }); + }); + + describe("when input restriction is given", () => { + // overriding the components's template + beforeEach( + fakeAsync(() => { + // the directive should not be used with square brackets "[]" because the input is an string literal! + const newTemplate: string = getTemplate("starkRestrictInput='\\d'"); + + TestBed.overrideTemplate(TestComponent, newTemplate); + + // compile template and css + return TestBed.compileComponents(); + }) + ); + + beforeEach(() => { + initializeComponentFixture(); + }); + + it("should prevent any value other than the given ones in the configuration from being typed in the input", () => { + const inputElement: DebugElement = fixture.debugElement.query(By.css("input")); + + let keyPressEvent: KeyboardEvent = triggerKeyPressEvent(inputElement, "a"); + expect(keyPressEvent.defaultPrevented).toBe(true); + + keyPressEvent = triggerKeyPressEvent(inputElement, "B"); + expect(keyPressEvent.defaultPrevented).toBe(true); + + keyPressEvent = triggerKeyPressEvent(inputElement, "-"); + expect(keyPressEvent.defaultPrevented).toBe(true); + }); + + it("should NOT prevent any of the values given in the configuration from being typed in the input", () => { + const inputElement: DebugElement = fixture.debugElement.query(By.css("input")); + + let keyPressEvent: Event = triggerKeyPressEvent(inputElement, "1"); + expect(keyPressEvent.defaultPrevented).toBe(false); + + keyPressEvent = triggerKeyPressEvent(inputElement, "9"); + expect(keyPressEvent.defaultPrevented).toBe(false); + + keyPressEvent = triggerKeyPressEvent(inputElement, "0"); + expect(keyPressEvent.defaultPrevented).toBe(false); + }); + }); +}); diff --git a/packages/stark-ui/src/modules/keyboard-directives/directives/restrict-input.directive.ts b/packages/stark-ui/src/modules/keyboard-directives/directives/restrict-input.directive.ts new file mode 100644 index 0000000000..c705f3c9bf --- /dev/null +++ b/packages/stark-ui/src/modules/keyboard-directives/directives/restrict-input.directive.ts @@ -0,0 +1,56 @@ +import { Directive, HostListener, Inject, Input, OnInit } from "@angular/core"; +import { STARK_LOGGING_SERVICE, StarkLoggingService } from "@nationalbankbelgium/stark-core"; + +/** + * Name of the directive + */ +const directiveName: string = "[starkRestrictInput]"; + +/** + * Directive to restrict the characters that can be typed in a field to allow only those matching a regex pattern. + */ +@Directive({ + selector: directiveName +}) +export class StarkRestrictInputDirective implements OnInit { + /** + * A valid regular expression that defines the allowed characters + */ + /* tslint:disable:no-input-rename */ + @Input("starkRestrictInput") public inputRestriction: string; + + /** + * Event handler to be invoked on a "keypress" event in the field + */ + @HostListener("keypress", ["$event"]) + public eventHandler(event: KeyboardEvent): boolean { + const regularExpression: string = this.inputRestriction || ""; + + if (regularExpression) { + const key: string = String.fromCharCode(!event.charCode ? event.which : event.charCode); + const regex: RegExp = new RegExp(regularExpression); + + if (!regex.test(key)) { + event.preventDefault(); + return false; + } + } else { + this.logger.warn(directiveName + ": no input restriction defined"); + } + + return true; + } + + /** + * Class constructor + * @param logger - The logger of the application + */ + public constructor(@Inject(STARK_LOGGING_SERVICE) private logger: StarkLoggingService) {} + + /** + * Directive lifecycle hook + */ + public ngOnInit(): void { + this.logger.debug(directiveName + ": directive initialized"); + } +} 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 1d690da149..007412c877 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 } from "./directives"; +import { StarkOnEnterKeyDirective, StarkRestrictInputDirective } from "./directives"; @NgModule({ - declarations: [StarkOnEnterKeyDirective], - exports: [StarkOnEnterKeyDirective] + declarations: [StarkOnEnterKeyDirective, StarkRestrictInputDirective], + exports: [StarkOnEnterKeyDirective, StarkRestrictInputDirective] }) export class StarkKeyboardDirectivesModule {} diff --git a/showcase/src/app/demo/keyboard-directives/keyboard-directives.component.html b/showcase/src/app/demo/keyboard-directives/keyboard-directives.component.html index b495013d78..935efa0d22 100644 --- a/showcase/src/app/demo/keyboard-directives/keyboard-directives.component.html +++ b/showcase/src/app/demo/keyboard-directives/keyboard-directives.component.html @@ -1,23 +1,26 @@ + title="SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.ON_ENTER_KEY.TITLE">
-

SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.DESCRIPTION

+

SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.ON_ENTER_KEY.DESCRIPTION

- SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.INPUT_WITH_CONTEXT - SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.ON_ENTER_KEY.INPUT_WITH_CONTEXT + - SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.INPUT_WITHOUT_CONTEXT + SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.ON_ENTER_KEY.INPUT_WITHOUT_CONTEXT - SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.INPUT_WITHOUT_CONTEXT_ALTERNATIVE - SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.ON_ENTER_KEY.INPUT_WITHOUT_CONTEXT_ALTERNATIVE + + @@ -25,3 +28,36 @@
{{ logging }}
+ + + +

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_NO_SPECIAL_CHARACTERS + + + + + SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.RESTRICT_INPUT.INPUT_UPPERCASE_CHARACTERS + + + +
diff --git a/showcase/src/app/demo/keyboard-directives/keyboard-directives.component.scss b/showcase/src/app/demo/keyboard-directives/keyboard-directives.component.scss index 5140e7f346..9bed527bce 100644 --- a/showcase/src/app/demo/keyboard-directives/keyboard-directives.component.scss +++ b/showcase/src/app/demo/keyboard-directives/keyboard-directives.component.scss @@ -3,6 +3,11 @@ flex-direction: column; } +.restrict-input-directive-form { + display: flex; + flex-direction: column; +} + pre code { display: block; height: 200px; diff --git a/showcase/src/assets/examples/keyboard-directives/restrict-input-directive.html b/showcase/src/assets/examples/keyboard-directives/restrict-input-directive.html new file mode 100644 index 0000000000..b4fbbc34d3 --- /dev/null +++ b/showcase/src/assets/examples/keyboard-directives/restrict-input-directive.html @@ -0,0 +1,29 @@ +
+

Type some value in the inputs and some characters will be prevented depending on the restriction set in every field

+ + Accept only numbers + + + + Accept only alphanumeric characters + + + + + Accept all except special characters + + + + + Accept only uppercase characters + + +
diff --git a/showcase/src/assets/examples/keyboard-directives/restrict-input-directive.ts b/showcase/src/assets/examples/keyboard-directives/restrict-input-directive.ts new file mode 100644 index 0000000000..e72c2299bc --- /dev/null +++ b/showcase/src/assets/examples/keyboard-directives/restrict-input-directive.ts @@ -0,0 +1,11 @@ +import { Component, Inject } from "@angular/core"; +import { STARK_LOGGING_SERVICE, StarkLoggingService } from "@nationalbankbelgium/stark-core"; + +@Component({ + selector: "showcase-demo-on-enter-key", + styleUrls: ["./keyboard-directives.component.scss"], + templateUrl: "./keyboard-directives.component.html" +}) +export class KeyboardDirectivesComponent { + public constructor(@Inject(STARK_LOGGING_SERVICE) public logger: StarkLoggingService) {} +} diff --git a/showcase/src/assets/translations/en.json b/showcase/src/assets/translations/en.json index 7346017a44..89e70353ca 100644 --- a/showcase/src/assets/translations/en.json +++ b/showcase/src/assets/translations/en.json @@ -7,13 +7,24 @@ "ALT": "Action Bar Alternative" }, "KEYBOARD_DIRECTIVES": { - "ON_ENTER_KEY_DIRECTIVE": "On Enter Key Directive", - "DESCRIPTION": "Type some value in the inputs and then press Enter in any of them to trigger the callback function", - "INPUT_WITH_CONTEXT": "With bound context", - "INPUT_WITHOUT_CONTEXT": "Without bound context (bound values are not accessible)", - "INPUT_WITHOUT_CONTEXT_ALTERNATIVE": "Without bound context but passed as parameter", - "TYPE_AND_PRESS_ENTER": "Type a value and press Enter", - "TYPE_PRESS_ENTER_AND_CHECK_CONSOLE": "Type a value, press Enter and check the console" + "ON_ENTER_KEY": { + "TITLE": "On Enter Key Directive", + "DESCRIPTION": "Type some value in the inputs and then press Enter in any of them to trigger the callback function", + "INPUT_WITH_CONTEXT": "With bound context", + "INPUT_WITHOUT_CONTEXT": "Without bound context (bound values are not accessible)", + "INPUT_WITHOUT_CONTEXT_ALTERNATIVE": "Without bound context but passed as parameter", + "TYPE_AND_PRESS_ENTER": "Type a value and press Enter", + "TYPE_PRESS_ENTER_AND_CHECK_CONSOLE": "Type a value, press Enter and check the console" + }, + "RESTRICT_INPUT": { + "TITLE": "Restrict Input Directive", + "DESCRIPTION": "Type some value in the inputs and some characters will be prevented depending on the restriction set in every field", + "INPUT_ONLY_NUMBERS": "Accept only numbers", + "INPUT_ALPHANUMERICAL_CHARACTERS": "Accept only alphanumeric characters", + "INPUT_NO_SPECIAL_CHARACTERS": "Accept all except special characters", + "INPUT_UPPERCASE_CHARACTERS": "Accept only uppercase characters", + "TYPE_A_VALUE": "Type a value" + } } } } diff --git a/showcase/src/assets/translations/fr.json b/showcase/src/assets/translations/fr.json index ecb0cc10c9..7bb2cb1f84 100644 --- a/showcase/src/assets/translations/fr.json +++ b/showcase/src/assets/translations/fr.json @@ -7,13 +7,24 @@ "ALT": "Action Bar Alternative" }, "KEYBOARD_DIRECTIVES": { - "ON_ENTER_KEY_DIRECTIVE": "On Enter Key Directive", - "DESCRIPTION": "Tapez une valeur dans les entrées, puis appuyez sur Entrée dans l'un d'eux pour déclencher la fonction callback", - "INPUT_WITH_CONTEXT": "Avec contexte lié", - "INPUT_WITHOUT_CONTEXT": "Sans contexte lié (les valeurs liées ne sont pas accessibles)", - "INPUT_WITHOUT_CONTEXT_ALTERNATIVE": "Sans contexte lié mais passé en paramètre", - "TYPE_AND_PRESS_ENTER": "Tapez une valeur et appuyez sur Entrée", - "TYPE_PRESS_ENTER_AND_CHECK_CONSOLE": "Tapez une valeur, appuyez sur Entrée et vérifiez la console" + "ON_ENTER_KEY": { + "TITLE": "On Enter Key Directive", + "DESCRIPTION": "Tapez une valeur dans les entrées, puis appuyez sur Entrée dans l'un d'eux pour déclencher la fonction callback", + "INPUT_WITH_CONTEXT": "Avec contexte lié", + "INPUT_WITHOUT_CONTEXT": "Sans contexte lié (les valeurs liées ne sont pas accessibles)", + "INPUT_WITHOUT_CONTEXT_ALTERNATIVE": "Sans contexte lié mais passé en paramètre", + "TYPE_AND_PRESS_ENTER": "Tapez une valeur et appuyez sur Entrée", + "TYPE_PRESS_ENTER_AND_CHECK_CONSOLE": "Tapez une valeur, appuyez sur Entrée et vérifiez la console" + }, + "RESTRICT_INPUT": { + "TITLE": "Restrict Input Directive", + "DESCRIPTION": "Tapez une valeur dans les entrées et certains caractères seront prévenus en fonction de l'ensemble de restriction dans chaque champ", + "INPUT_ONLY_NUMBERS": "Accepter seulement les nombres", + "INPUT_ALPHANUMERICAL_CHARACTERS": "Accepter uniquement les caractères alphanumériques", + "INPUT_NO_SPECIAL_CHARACTERS": "Accepter tous les caractères sauf les caractères spéciaux", + "INPUT_UPPERCASE_CHARACTERS": "Accepter uniquement les majuscules", + "TYPE_A_VALUE": "Taper une valeur" + } } } } diff --git a/showcase/src/assets/translations/nl.json b/showcase/src/assets/translations/nl.json index 88083962ab..d9039cc1bc 100644 --- a/showcase/src/assets/translations/nl.json +++ b/showcase/src/assets/translations/nl.json @@ -7,13 +7,24 @@ "ALT": "Action Bar Alternative" }, "KEYBOARD_DIRECTIVES": { - "ON_ENTER_KEY_DIRECTIVE": "On Enter Key Directive", - "DESCRIPTION": "Typ een waarde in de invulvelden en druk vervolgens op Enter om de callback functie te activeren", - "INPUT_WITH_CONTEXT": "Met verbonden context", - "INPUT_WITHOUT_CONTEXT": "Zonder verbonden context (verbonden waarden zijn niet toegankelijk)", - "INPUT_WITHOUT_CONTEXT_ALTERNATIVE": "Zonder verbonden context maar doorgegeven als parameter", - "TYPE_AND_PRESS_ENTER": "Typ een waarde en druk Enter", - "TYPE_PRESS_ENTER_AND_CHECK_CONSOLE": "Typ een waarde, druk Enter en controleer de console" + "ON_ENTER_KEY": { + "TITLE": "On Enter Key Directive", + "DESCRIPTION": "Typ een waarde in de invulvelden en druk vervolgens op Enter om de callback functie te activeren", + "INPUT_WITH_CONTEXT": "Met verbonden context", + "INPUT_WITHOUT_CONTEXT": "Zonder verbonden context (verbonden waarden zijn niet toegankelijk)", + "INPUT_WITHOUT_CONTEXT_ALTERNATIVE": "Zonder verbonden context maar doorgegeven als parameter", + "TYPE_AND_PRESS_ENTER": "Typ een waarde en druk Enter", + "TYPE_PRESS_ENTER_AND_CHECK_CONSOLE": "Typ een waarde, druk Enter en controleer de console" + }, + "RESTRICT_INPUT": { + "TITLE": "Restrict Input Directive", + "DESCRIPTION": "Typ een waarde in de input velden en sommige karakters zullen niet toegelaten worden door de restricties die op het veld werden gezet.", + "INPUT_ONLY_NUMBERS": "Aanvaard enkel cijfers", + "INPUT_ALPHANUMERICAL_CHARACTERS": "Aanvaard enkel alfanumerische karakters", + "INPUT_NO_SPECIAL_CHARACTERS": "Aanvaard alle karakters, behalve speciale karakters", + "INPUT_UPPERCASE_CHARACTERS": "Aanvaard enkel hoofdletters", + "TYPE_A_VALUE": "Geef een waarde in" + } } } }