From 032e6a1cefd00b228d18c3e8be6b3a581c84c171 Mon Sep 17 00:00:00 2001 From: christophercr Date: Tue, 24 Jul 2018 16:05:02 +0200 Subject: [PATCH] feat(stark-ui): implement RestrictInput directive and module ISSUES CLOSED: #546 --- packages/stark-ui/src/modules.ts | 1 + .../src/modules/keyboard-directives.ts | 2 + .../modules/keyboard-directives/directives.ts | 1 + .../restrict-input.directive.spec.ts | 122 ++++++++++++++++++ .../directives/restrict-input.directive.ts | 56 ++++++++ .../keyboard-directives.module.ts | 8 ++ showcase/src/app/app.component.html | 3 + showcase/src/app/app.routes.ts | 3 +- showcase/src/app/demo/demo.module.ts | 27 +++- showcase/src/app/demo/index.ts | 1 + .../src/app/demo/keyboard-directives/index.ts | 1 + .../keyboard-directives.component.html | 32 +++++ .../keyboard-directives.component.scss | 5 + .../keyboard-directives.component.ts | 11 ++ .../restrict-input-directive.html | 29 +++++ .../restrict-input-directive.ts | 11 ++ showcase/src/assets/translations/en.json | 11 ++ showcase/src/assets/translations/fr.json | 11 ++ showcase/src/assets/translations/nl.json | 11 ++ 19 files changed, 342 insertions(+), 4 deletions(-) create mode 100644 packages/stark-ui/src/modules/keyboard-directives.ts create mode 100644 packages/stark-ui/src/modules/keyboard-directives/directives.ts create mode 100644 packages/stark-ui/src/modules/keyboard-directives/directives/restrict-input.directive.spec.ts create mode 100644 packages/stark-ui/src/modules/keyboard-directives/directives/restrict-input.directive.ts create mode 100644 packages/stark-ui/src/modules/keyboard-directives/keyboard-directives.module.ts create mode 100644 showcase/src/app/demo/keyboard-directives/index.ts create mode 100644 showcase/src/app/demo/keyboard-directives/keyboard-directives.component.html create mode 100644 showcase/src/app/demo/keyboard-directives/keyboard-directives.component.scss create mode 100644 showcase/src/app/demo/keyboard-directives/keyboard-directives.component.ts create mode 100644 showcase/src/assets/examples/keyboard-directives/restrict-input-directive.html create mode 100644 showcase/src/assets/examples/keyboard-directives/restrict-input-directive.ts diff --git a/packages/stark-ui/src/modules.ts b/packages/stark-ui/src/modules.ts index 3f130849c3..5859f2de5d 100644 --- a/packages/stark-ui/src/modules.ts +++ b/packages/stark-ui/src/modules.ts @@ -1,5 +1,6 @@ export * from "./modules/action-bar"; export * from "./modules/app-logo"; +export * from "./modules/keyboard-directives"; export * from "./modules/pretty-print"; export * from "./modules/slider"; export * from "./modules/table"; diff --git a/packages/stark-ui/src/modules/keyboard-directives.ts b/packages/stark-ui/src/modules/keyboard-directives.ts new file mode 100644 index 0000000000..7348cb9f6f --- /dev/null +++ b/packages/stark-ui/src/modules/keyboard-directives.ts @@ -0,0 +1,2 @@ +export * from "./keyboard-directives/keyboard-directives.module"; +export * from "./keyboard-directives/directives"; diff --git a/packages/stark-ui/src/modules/keyboard-directives/directives.ts b/packages/stark-ui/src/modules/keyboard-directives/directives.ts new file mode 100644 index 0000000000..364fd341cf --- /dev/null +++ b/packages/stark-ui/src/modules/keyboard-directives/directives.ts @@ -0,0 +1 @@ +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 new file mode 100644 index 0000000000..30e0615b12 --- /dev/null +++ b/packages/stark-ui/src/modules/keyboard-directives/keyboard-directives.module.ts @@ -0,0 +1,8 @@ +import { NgModule } from "@angular/core"; +import { StarkRestrictInputDirective } from "./directives"; + +@NgModule({ + declarations: [StarkRestrictInputDirective], + exports: [StarkRestrictInputDirective] +}) +export class StarkKeyboardDirectivesModule {} diff --git a/showcase/src/app/app.component.html b/showcase/src/app/app.component.html index a544d50811..5187b7bb4d 100644 --- a/showcase/src/app/app.component.html +++ b/showcase/src/app/app.component.html @@ -37,6 +37,9 @@ Example Viewer + + Keyboard Directives + Table diff --git a/showcase/src/app/app.routes.ts b/showcase/src/app/app.routes.ts index e371f6e104..ff24adb266 100644 --- a/showcase/src/app/app.routes.ts +++ b/showcase/src/app/app.routes.ts @@ -1,4 +1,4 @@ -import { ActionBarComponent, ButtonComponent, ExampleViewerComponent, TableComponent } from "./demo"; +import { ActionBarComponent, ButtonComponent, ExampleViewerComponent, KeyboardDirectivesComponent, TableComponent } from "./demo"; import { HomeComponent } from "./home"; import { NoContentComponent } from "./no-content"; import { Ng2StateDeclaration } from "@uirouter/angular"; @@ -8,6 +8,7 @@ export const APP_STATES: Ng2StateDeclaration[] = [ { name: "demo-action-bar", url: "/demo/action-bar", component: ActionBarComponent }, { name: "demo-button", url: "/demo/button", component: ButtonComponent }, { name: "demo-example-viewer", url: "/demo/example-viewer", component: ExampleViewerComponent }, + { name: "demo-keyboard-directives", url: "/demo/keyboard-directives", component: KeyboardDirectivesComponent }, { name: "demo-table", url: "/demo/table", component: TableComponent }, { name: "otherwise", url: "/otherwise", component: NoContentComponent } ]; diff --git a/showcase/src/app/demo/demo.module.ts b/showcase/src/app/demo/demo.module.ts index 643699269c..3fcbad62fe 100644 --- a/showcase/src/app/demo/demo.module.ts +++ b/showcase/src/app/demo/demo.module.ts @@ -1,31 +1,52 @@ -import { MatButtonModule, MatCardModule, MatIconModule, MatTabsModule, MatTooltipModule, MatSnackBarModule } from "@angular/material"; +import { + MatButtonModule, + MatCardModule, + MatIconModule, + MatTabsModule, + MatTooltipModule, + MatSnackBarModule, + MatInputModule, + MatFormFieldModule +} from "@angular/material"; import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; +import { FormsModule } from "@angular/forms"; import { TranslateModule } from "@ngx-translate/core"; import { ActionBarComponent } from "./action-bar/action-bar.component"; import { ButtonComponent } from "./button/button.component"; import { ExampleViewerComponent } from "./example-viewer/example-viewer.component"; +import { KeyboardDirectivesComponent } from "./keyboard-directives/keyboard-directives.component"; import { TableComponent } from "./table/table.component"; import { SharedModule } from "../shared/shared.module"; -import { StarkActionBarModule, StarkSliderModule, StarkTableModule, StarkSvgViewBoxModule } from "@nationalbankbelgium/stark-ui"; +import { + StarkActionBarModule, + StarkSliderModule, + StarkTableModule, + StarkSvgViewBoxModule, + StarkKeyboardDirectivesModule +} from "@nationalbankbelgium/stark-ui"; @NgModule({ imports: [ MatButtonModule, MatCardModule, + MatFormFieldModule, MatIconModule, + MatInputModule, MatTooltipModule, MatSnackBarModule, MatTabsModule, CommonModule, + FormsModule, TranslateModule, SharedModule, StarkActionBarModule, StarkSliderModule, StarkSvgViewBoxModule, + StarkKeyboardDirectivesModule, StarkTableModule ], - declarations: [ActionBarComponent, ButtonComponent, ExampleViewerComponent, TableComponent], + declarations: [ActionBarComponent, ButtonComponent, ExampleViewerComponent, KeyboardDirectivesComponent, TableComponent], exports: [ActionBarComponent, ButtonComponent, ExampleViewerComponent, TableComponent] }) export class DemoModule {} diff --git a/showcase/src/app/demo/index.ts b/showcase/src/app/demo/index.ts index f9d77c5c6e..b8f2773320 100644 --- a/showcase/src/app/demo/index.ts +++ b/showcase/src/app/demo/index.ts @@ -1,5 +1,6 @@ export * from "./action-bar"; export * from "./button"; export * from "./example-viewer"; +export * from "./keyboard-directives"; export * from "./table"; export * from "./demo.module"; diff --git a/showcase/src/app/demo/keyboard-directives/index.ts b/showcase/src/app/demo/keyboard-directives/index.ts new file mode 100644 index 0000000000..436d3f7d27 --- /dev/null +++ b/showcase/src/app/demo/keyboard-directives/index.ts @@ -0,0 +1 @@ +export * from "./keyboard-directives.component"; diff --git a/showcase/src/app/demo/keyboard-directives/keyboard-directives.component.html b/showcase/src/app/demo/keyboard-directives/keyboard-directives.component.html new file mode 100644 index 0000000000..f8eebf2f46 --- /dev/null +++ b/showcase/src/app/demo/keyboard-directives/keyboard-directives.component.html @@ -0,0 +1,32 @@ + +
+

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 new file mode 100644 index 0000000000..b78e66ecba --- /dev/null +++ b/showcase/src/app/demo/keyboard-directives/keyboard-directives.component.scss @@ -0,0 +1,5 @@ +.restrict-input-directive-form { + display: flex; + flex-direction: column; +} + diff --git a/showcase/src/app/demo/keyboard-directives/keyboard-directives.component.ts b/showcase/src/app/demo/keyboard-directives/keyboard-directives.component.ts new file mode 100644 index 0000000000..e72c2299bc --- /dev/null +++ b/showcase/src/app/demo/keyboard-directives/keyboard-directives.component.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/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 997b2ef07b..e0810ada96 100644 --- a/showcase/src/assets/translations/en.json +++ b/showcase/src/assets/translations/en.json @@ -5,6 +5,17 @@ "CLASSIC-FULL": "Action Bar Classic Full", "CLASSIC-COMPACT": "Action Bar Classic Compact", "ALT": "Action Bar Alternative" + }, + "KEYBOARD_DIRECTIVES": { + "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 997b2ef07b..4ad2156429 100644 --- a/showcase/src/assets/translations/fr.json +++ b/showcase/src/assets/translations/fr.json @@ -5,6 +5,17 @@ "CLASSIC-FULL": "Action Bar Classic Full", "CLASSIC-COMPACT": "Action Bar Classic Compact", "ALT": "Action Bar Alternative" + }, + "KEYBOARD_DIRECTIVES": { + "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 997b2ef07b..dda42afac7 100644 --- a/showcase/src/assets/translations/nl.json +++ b/showcase/src/assets/translations/nl.json @@ -5,6 +5,17 @@ "CLASSIC-FULL": "Action Bar Classic Full", "CLASSIC-COMPACT": "Action Bar Classic Compact", "ALT": "Action Bar Alternative" + }, + "KEYBOARD_DIRECTIVES": { + "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" + } } } }