Skip to content

Commit

Permalink
feat(stark-ui): implement RestrictInput directive and module
Browse files Browse the repository at this point in the history
ISSUES CLOSED: #546 #550
  • Loading branch information
christophercr committed Jul 27, 2018
1 parent d82a11e commit b8bd5be
Show file tree
Hide file tree
Showing 11 changed files with 325 additions and 32 deletions.
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./directives/on-enter-key.directive";
export * from "./directives/restrict-input.directive";
Original file line number Diff line number Diff line change
@@ -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<TestComponent>;

function getTemplate(restrictInputDirective: string): string {
return "<input " + "type='text' " + restrictInputDirective + ">";
}

function initializeComponentFixture(): void {
fixture = TestBed.createComponent(TestComponent);
// trigger initial data binding
fixture.detectChanges();
}

function triggerKeyPressEvent(inputElement: DebugElement, value: string): KeyboardEvent {
(<HTMLInputElement>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 <KeyboardEvent>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);
});
});
});
Original file line number Diff line number Diff line change
@@ -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");
}
}
Original file line number Diff line number Diff line change
@@ -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 {}
Original file line number Diff line number Diff line change
@@ -1,27 +1,63 @@
<example-viewer [extensions]="['HTML', 'TS']" filesPath="keyboard-directives/on-enter-key-directive"
title="SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.ON_ENTER_KEY_DIRECTIVE">
title="SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.ON_ENTER_KEY.TITLE">
<form class="on-enter-key-directive-form">
<p translate>SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.DESCRIPTION</p>
<p translate>SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.ON_ENTER_KEY.DESCRIPTION</p>
<mat-form-field>
<mat-label translate>SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.INPUT_WITH_CONTEXT</mat-label>
<input name="test" [(ngModel)]="inputValue1" matInput placeholder="{{ 'SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.TYPE_AND_PRESS_ENTER' | translate }}"
<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' }]">
</mat-form-field>
<mat-form-field>
<mat-label translate>SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.INPUT_WITHOUT_CONTEXT</mat-label>
<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.TYPE_PRESS_ENTER_AND_CHECK_CONSOLE' | translate }}"
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.INPUT_WITHOUT_CONTEXT_ALTERNATIVE</mat-label>
<input name="test" [(ngModel)]="inputValue3" matInput placeholder="{{ 'SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.TYPE_AND_PRESS_ENTER' | translate }}"
<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]">
</mat-form-field>
</form>
<label>Logging:</label>
<pre><code>{{ logging }}</code></pre>
</example-viewer>

<example-viewer [extensions]="['HTML', 'TS']" filesPath="keyboard-directives/restrict-input-directive"
title="SHOWCASE.DEMO.KEYBOARD_DIRECTIVES.RESTRICT_INPUT.TITLE">
<form class="restrict-input-directive-form">
<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">
</mat-form-field>
<mat-form-field>
<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]*$">
</mat-form-field>
<mat-form-field>
<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">
</mat-form-field>
<mat-form-field>
<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]*$">
</mat-form-field>
</form>
</example-viewer>
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
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
@@ -0,0 +1,29 @@
<form class="restrict-input-directive-form">
<p>Type some value in the inputs and some characters will be prevented depending on the restriction set in every field</p>
<mat-form-field>
<mat-label>Accept only numbers</mat-label>
<input name="test" matInput
placeholder="Type a value"
starkRestrictInput="\d">
</mat-form-field>
<mat-form-field>
<mat-label>Accept only alphanumeric characters
</mat-label>
<input name="test" matInput
placeholder="Type a value"
starkRestrictInput="^[A-Za-z0-9]*$">
</mat-form-field>
<mat-form-field>
<mat-label>Accept all except special characters
</mat-label>
<input name="test" matInput
placeholder="Type a value"
starkRestrictInput="\w">
</mat-form-field>
<mat-form-field>
<mat-label>Accept only uppercase characters</mat-label>
<input name="test" matInput
placeholder="Type a value"
starkRestrictInput="^[A-Z]*$">
</mat-form-field>
</form>
Original file line number Diff line number Diff line change
@@ -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) {}
}
25 changes: 18 additions & 7 deletions showcase/src/assets/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
}
}
Expand Down
25 changes: 18 additions & 7 deletions showcase/src/assets/translations/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
}
}
Expand Down
Loading

0 comments on commit b8bd5be

Please sign in to comment.