aboutsummaryrefslogtreecommitdiffstats
path: root/src/angular/form-elements
diff options
context:
space:
mode:
Diffstat (limited to 'src/angular/form-elements')
-rw-r--r--src/angular/form-elements/checkbox/checkbox.component.html.ts8
-rw-r--r--src/angular/form-elements/checkbox/checkbox.component.spec.ts37
-rw-r--r--src/angular/form-elements/checkbox/checkbox.component.ts21
-rw-r--r--src/angular/form-elements/dropdown/dropdown-models.ts18
-rw-r--r--src/angular/form-elements/dropdown/dropdown-trigger.directive.ts17
-rw-r--r--src/angular/form-elements/dropdown/dropdown.component.html.ts59
-rw-r--r--src/angular/form-elements/dropdown/dropdown.component.spec.ts71
-rw-r--r--src/angular/form-elements/dropdown/dropdown.component.ts149
-rw-r--r--src/angular/form-elements/form-elements.module.ts38
-rw-r--r--src/angular/form-elements/input/input.component.html.ts19
-rw-r--r--src/angular/form-elements/input/input.component.ts54
-rw-r--r--src/angular/form-elements/radios/radio-button.model.ts15
-rw-r--r--src/angular/form-elements/radios/radio-buttons-group.component.html.ts20
-rw-r--r--src/angular/form-elements/radios/radio-buttons-group.component.spec.ts52
-rw-r--r--src/angular/form-elements/radios/radio-buttons-group.component.ts52
-rw-r--r--src/angular/form-elements/validation/validatable.component.ts25
-rw-r--r--src/angular/form-elements/validation/validatable.interface.ts5
-rw-r--r--src/angular/form-elements/validation/validation-group.component.html.ts3
-rw-r--r--src/angular/form-elements/validation/validation-group.component.ts47
-rw-r--r--src/angular/form-elements/validation/validation.component.html.ts3
-rw-r--r--src/angular/form-elements/validation/validation.component.ts79
-rw-r--r--src/angular/form-elements/validation/validation.module.ts35
-rw-r--r--src/angular/form-elements/validation/validators/base.validator.component.html.ts10
-rw-r--r--src/angular/form-elements/validation/validators/base.validator.component.ts25
-rw-r--r--src/angular/form-elements/validation/validators/custom.validator.component.ts23
-rw-r--r--src/angular/form-elements/validation/validators/regex.validator.component.ts24
-rw-r--r--src/angular/form-elements/validation/validators/required.validator.component.ts25
-rw-r--r--src/angular/form-elements/validation/validators/validator.interface.ts3
28 files changed, 937 insertions, 0 deletions
diff --git a/src/angular/form-elements/checkbox/checkbox.component.html.ts b/src/angular/form-elements/checkbox/checkbox.component.html.ts
new file mode 100644
index 0000000..f4031db
--- /dev/null
+++ b/src/angular/form-elements/checkbox/checkbox.component.html.ts
@@ -0,0 +1,8 @@
+export default `
+<div class="sdc-checkbox">
+ <label SdcRippleClickAnimation [rippleClickDisabled]="disabled">
+ <input type="checkbox" class="sdc-checkbox__input" [ngModel]="checked" (ngModelChange)="toggleState($event)" [disabled]="disabled">
+ <span class="sdc-checkbox__label">{{ label }}</span>
+ </label>
+</div>
+`;
diff --git a/src/angular/form-elements/checkbox/checkbox.component.spec.ts b/src/angular/form-elements/checkbox/checkbox.component.spec.ts
new file mode 100644
index 0000000..36f478e
--- /dev/null
+++ b/src/angular/form-elements/checkbox/checkbox.component.spec.ts
@@ -0,0 +1,37 @@
+import { TestBed, async } from '@angular/core/testing';
+import { CheckboxComponent } from "./checkbox.component";
+import { AnimationDirectivesModule } from "../../animations/animation-directives.module";
+import { FormsModule } from "@angular/forms";
+
+
+describe("Checbox Tests", ()=>{
+ let component: CheckboxComponent;
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [
+ CheckboxComponent
+ ],
+ imports:[
+ FormsModule,
+ AnimationDirectivesModule
+ ]
+ }).compileComponents();
+ const fixture = TestBed.createComponent(CheckboxComponent);
+ component = fixture.componentInstance;
+ }));
+
+ it("Component Created", async(()=> {
+ expect(component).toBeDefined();
+ }));
+
+ it( "Test Value suppose to be toggled", async( ()=> {
+ component.toggleState(true)
+ expect(component.checked).toEqual(true);
+ }));
+
+ it( "If disabled not toggled"), async(()=>{
+ component.disabled = true;
+ component.toggleState(true);
+ expect(component.checked).toEqual(false);
+ });
+});
diff --git a/src/angular/form-elements/checkbox/checkbox.component.ts b/src/angular/form-elements/checkbox/checkbox.component.ts
new file mode 100644
index 0000000..ec05eac
--- /dev/null
+++ b/src/angular/form-elements/checkbox/checkbox.component.ts
@@ -0,0 +1,21 @@
+import { Component, Input, Output, EventEmitter, ViewEncapsulation } from '@angular/core';
+import template from "./checkbox.component.html";
+
+@Component({
+ selector: 'sdc-checkbox',
+ template: template,
+ encapsulation: ViewEncapsulation.None
+})
+export class CheckboxComponent {
+ @Input() label:string;
+ @Input() checked:boolean;
+ @Input() disabled:boolean;
+ @Output() checkedChange:EventEmitter<any> = new EventEmitter<any>();
+
+ public toggleState(newState:boolean) {
+ if (!this.disabled) {
+ this.checked = newState;
+ this.checkedChange.emit(newState);
+ }
+ }
+}
diff --git a/src/angular/form-elements/dropdown/dropdown-models.ts b/src/angular/form-elements/dropdown/dropdown-models.ts
new file mode 100644
index 0000000..fa8dc23
--- /dev/null
+++ b/src/angular/form-elements/dropdown/dropdown-models.ts
@@ -0,0 +1,18 @@
+export enum DropDownTypes {
+ Regular,
+ Headless,
+ Auto
+}
+
+export enum DropDownOptionType {
+ Simple, // default
+ Header,
+ Disable,
+ HorizontalLine
+}
+
+export interface IDropDownOption {
+ value: any;
+ label: string;
+ type?: DropDownOptionType;
+}
diff --git a/src/angular/form-elements/dropdown/dropdown-trigger.directive.ts b/src/angular/form-elements/dropdown/dropdown-trigger.directive.ts
new file mode 100644
index 0000000..94ab3bc
--- /dev/null
+++ b/src/angular/form-elements/dropdown/dropdown-trigger.directive.ts
@@ -0,0 +1,17 @@
+import { Directive, Input, HostBinding, HostListener } from "@angular/core";
+import { DropDownComponent } from "./dropdown.component";
+
+@Directive({
+ selector: '[SdcDropdownTrigger]'
+})
+
+export class DropDownTriggerDirective {
+
+ @HostBinding('class.js-sdc-dropdown--toggle-hook') true;
+ @Input() dropDown: DropDownComponent;
+
+ @HostListener('click', ['$event']) onClick = (event) => {
+ this.dropDown.toggleDropdown(event);
+ }
+
+}
diff --git a/src/angular/form-elements/dropdown/dropdown.component.html.ts b/src/angular/form-elements/dropdown/dropdown.component.html.ts
new file mode 100644
index 0000000..a4247a4
--- /dev/null
+++ b/src/angular/form-elements/dropdown/dropdown.component.html.ts
@@ -0,0 +1,59 @@
+export default `
+<div class="sdc-dropdown" #dropDownWrapper
+ [ngClass]="{
+ 'headless': type === cIDropDownTypes.Headless,
+ 'sdc-dropdown__error': !valid,
+ 'open-bottom': show && bottomVisible,
+ 'open-top':show && !bottomVisible}">
+ <label *ngIf="label" class="sdc-dropdown__label" [ngClass]="{'required':required}">{{label}}</label>
+ <div class="sdc-dropdown__component-container">
+
+ <!--[DROP-DOWN AUTO HEADER START]-->
+ <div *ngIf="type===cIDropDownTypes.Auto" class="sdc-dropdown-auto__wrapper">
+ <input class="sdc-dropdown__header js-sdc-dropdown--toggle-hook"
+ [(ngModel)]="this.filterValue"
+ (ngModelChange)="filterOptions(this.filterValue)"
+ placeholder="{{this.selectedOption?.label || this.selectedOption?.value || placeHolder}}">
+ <svg-icon name="caret1-down-o" mode="secondary" size="small" (click)="toggleDropdown($event)"></svg-icon>
+ </div>
+ <!--[DROP-DOWN AUTO HEADER END]-->
+
+ <!--[DROP-DOWN REGULAR HEADER START]-->
+ <button *ngIf="type===cIDropDownTypes.Regular"
+ class="sdc-dropdown__header js-sdc-dropdown--toggle-hook"
+ (click)="toggleDropdown($event)"
+ [ngClass]="{'disabled': disabled, 'placeholder':!this.selectedOption}">
+ {{ this.selectedOption?.label || this.selectedOption?.value || placeHolder}}
+ <svg-icon name="caret1-down-o" mode="secondary" size="small"></svg-icon>
+ </button>
+ <!--[DROP-DOWN HEADER END]-->
+
+ <!--[DROP-DOWN OPTIONS START]-->
+ <div class="sdc-dropdown__options-wrapper--frame" [ngClass]="{
+ 'sdc-dropdown__options-wrapper--top':!bottomVisible,
+ 'sdc-dropdown__options-wrapper--uncollapsed':show
+ }">
+ <ul #optionsContainerElement *ngIf="options" class="sdc-dropdown__options-list" [ngClass]="{
+ 'sdc-dropdown__options-list--headless': headless,
+ 'sdc-dropdown__options-list--animation-init':animation_init
+ }">
+ <ng-container *ngFor="let option of options; let i = index">
+ <!--[Drop down item list or string list start]-->
+ <li *ngIf="option" class="sdc-dropdown__option"
+ [ngClass]="{
+ 'selected': option == selectedOption,
+ 'sdc-dropdown__option--group':isGroupDesign,
+ 'sdc-dropdown__option--header': option.type && option.type === cIDropDownOptionType.Header,
+ 'sdc-dropdown__option--disabled': option.type && option.type === cIDropDownOptionType.Disable,
+ 'sdc-dropdown__option--hr': option.type && option.type === cIDropDownOptionType.HorizontalLine
+ }"
+ (click)="selectOption(option.value, $event)">{{option.label || String(option.value)}}</li>
+ <!--[Drop down item list or string list end]-->
+ </ng-container>
+ </ul>
+ </div>
+ <!--[DROP-DOWN OPTIONS END]-->
+
+ </div>
+</div>
+`;
diff --git a/src/angular/form-elements/dropdown/dropdown.component.spec.ts b/src/angular/form-elements/dropdown/dropdown.component.spec.ts
new file mode 100644
index 0000000..1c0cb4d
--- /dev/null
+++ b/src/angular/form-elements/dropdown/dropdown.component.spec.ts
@@ -0,0 +1,71 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { DropDownComponent } from './dropdown.component';
+import { IDropDownOption, DropDownTypes } from "./dropdown-models";
+import { FormsModule } from "@angular/forms";
+import {SvgIconModule} from "../../svg-icon/svg-icon.module";
+
+
+const label:string = "DropDown example";
+const placeHolder:string = "Please choose option";
+const options:IDropDownOption[] = [
+ {
+ label:'First Option',
+ value: 'First Option'
+ },
+ {
+ label:'Second Option',
+ value: 'Second Option'
+ },
+ {
+ label:'Third Option',
+ value: 'Third Option'
+ }
+];
+
+describe('DropDown component', () => {
+ let fixture: ComponentFixture<DropDownComponent>;
+ let component: DropDownComponent;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ DropDownComponent ],
+ imports:[
+ FormsModule,
+ SvgIconModule
+ ]
+ }).compileComponents();
+ fixture = TestBed.createComponent(DropDownComponent);
+ component = fixture.componentInstance;
+
+ }));
+
+ beforeEach(()=>{
+ component.label = label;
+ component.placeHolder = placeHolder;
+ component.options = options;
+ component.type = DropDownTypes.Regular;
+ console.log('herer we got component', component)
+ fixture.detectChanges();
+ });
+
+ it('component should be created', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('component should export the selected value', () => {
+ const option = options[1];
+ component.selectOption(option);
+ fixture.detectChanges();
+ expect(component.selectedOption).toEqual(option);
+ });
+
+ it('component should have autocomplite', () => {
+ expect(component.options.length).toEqual(3);
+ component.type = DropDownTypes.Auto;
+ component.filterValue = 'testERrorotesttresadfadfasdfasf';
+ fixture.detectChanges();
+ component.filterOptions(component.filterValue);
+ expect(component.options.length).toEqual(0);
+ });
+
+});
diff --git a/src/angular/form-elements/dropdown/dropdown.component.ts b/src/angular/form-elements/dropdown/dropdown.component.ts
new file mode 100644
index 0000000..a23072f
--- /dev/null
+++ b/src/angular/form-elements/dropdown/dropdown.component.ts
@@ -0,0 +1,149 @@
+import { Component, EventEmitter, Input, Output, forwardRef, OnChanges, SimpleChanges, OnInit, ElementRef, ViewChild, AfterViewInit, HostListener, Renderer } from '@angular/core';
+import { IDropDownOption, DropDownOptionType, DropDownTypes } from "./dropdown-models";
+import { ValidatableComponent } from './../validation/validatable.component';
+import template from './dropdown.component.html';
+
+@Component({
+ selector: 'sdc-dropdown',
+ template: template
+})
+export class DropDownComponent extends ValidatableComponent implements OnChanges, OnInit {
+
+ @Output('changed') changeEmitter:EventEmitter<IDropDownOption> = new EventEmitter<IDropDownOption>();
+ @Input() label: string;
+ @Input() options: IDropDownOption[];
+ @Input() disabled: boolean;
+ @Input() placeHolder: string;
+ @Input() required: boolean;
+ @Input() maxHeight: number;
+ @Input() selectedOption: IDropDownOption;
+ @Input() type: DropDownTypes = DropDownTypes.Regular;
+ @ViewChild('dropDownWrapper') dropDownWrapper: ElementRef;
+ @ViewChild('optionsContainerElement') optionsContainerElement: ElementRef;
+ @HostListener('document:click', ['$event']) onClick(e) {
+ this.onClickDocument(e);
+ }
+
+ private bottomVisible = true;
+ private myRenderer: Renderer;
+
+ // Drop-down show/hide flag. default is false (closed)
+ public show = false;
+
+ // Export DropDownOptionType enum so we can use it on the template
+ public cIDropDownOptionType = DropDownOptionType;
+ public cIDropDownTypes = DropDownTypes;
+
+ // Configure unselectable option types
+ private unselectableOptions = [
+ DropDownOptionType.Disable,
+ DropDownOptionType.Header,
+ DropDownOptionType.HorizontalLine
+ ];
+
+ // Set or unset Group style on drop-down
+ public isGroupDesign = false;
+ public animation_init = false;
+ public allOptions: IDropDownOption[];
+ public filterValue: string;
+
+ constructor(public renderer: Renderer) {
+ super();
+ this.myRenderer = renderer;
+ this.maxHeight = 244;
+ this.filterValue = '';
+ }
+
+ ngOnInit(): void {
+ if (this.options) {
+ this.allOptions = this.options;
+ if (this.options.find(option => option.type === DropDownOptionType.Header)) {
+ this.isGroupDesign = true;
+ }
+ }
+ }
+
+ ngOnChanges(changes: SimpleChanges): void {
+ console.log("ngOnChanges");
+ if (changes.selectedOption && changes.selectedOption.currentValue !== changes.selectedOption.previousValue) {
+ if (typeof changes.selectedOption.currentValue === 'string' && this.isSelectable(changes.selectedOption.currentValue)) {
+ this.setSelected(changes.selectedOption.currentValue);
+ } else if (this.isSelectable(changes.selectedOption.currentValue.value)) {
+ this.setSelected(changes.selectedOption.currentValue.value);
+ } else {
+ this.setSelected(undefined);
+ }
+ }
+ }
+
+ public getValue(): any {
+ return this.selectedOption && this.selectedOption.value;
+ }
+
+ public selectOption = (option: IDropDownOption | string, event?): void => {
+ if (event) { event.stopPropagation(); }
+ if (this.type === DropDownTypes.Headless) {
+ // Hide the options when in headless mode and user select option.
+ this.myRenderer.setElementStyle(this.dropDownWrapper.nativeElement, 'display', 'none');
+ }
+ if (typeof option === 'string' && this.isSelectable(option)) {
+ this.setSelected(option);
+ } else if (this.isSelectable((option as IDropDownOption).value)) {
+ this.setSelected((option as IDropDownOption).value);
+ }
+ }
+
+ public toggleDropdown = (event?): void => {
+ if (event) { event.stopPropagation(); }
+ if (this.type === DropDownTypes.Headless) {
+ // Show the options when in headless mode.
+ this.myRenderer.setElementStyle(this.dropDownWrapper.nativeElement, 'display', 'block');
+ }
+ if (this.disabled) { return; }
+ this.animation_init = true;
+ this.bottomVisible = this.isBottomVisible();
+ this.show = !this.show;
+ }
+
+ public filterOptions = (filterValue): void => {
+ if (filterValue.length >= 1 && !this.show) { this.toggleDropdown(); }
+ if (this.selectedOption) { this.selectedOption = null; }
+ this.options = this.allOptions.filter((option) => {
+ return option.value.toLowerCase().indexOf(filterValue.toLowerCase()) > -1;
+ });
+ }
+
+ private isSelectable = (value: string): boolean => {
+ const option: IDropDownOption = this.options.find(o => o.value === value);
+ if (!option) { return false; }
+ if (!option.type) { return true; }
+ return !this.unselectableOptions.find(optionType => optionType === option.type);
+ }
+
+ private setSelected = (value: string): void => {
+ this.selectedOption = this.options.find(o => o.value === value);
+ if (this.type === DropDownTypes.Auto) { this.filterValue = value; }
+ this.show = false;
+ this.changeEmitter.next(this.selectedOption);
+ }
+
+ private isBottomVisible = (): boolean => {
+ const windowPos = window.innerHeight + window.pageYOffset;
+ const boundingRect = this.dropDownWrapper.nativeElement.getBoundingClientRect();
+ const dropDownPos = boundingRect.top + boundingRect.height + this.maxHeight;
+ return windowPos > dropDownPos;
+ }
+
+ private onClickDocument = (event): void => {
+ if (this.type === DropDownTypes.Headless) {
+ if (!this.optionsContainerElement.nativeElement.contains(event.target)) {
+ this.show = false;
+ }
+ } else {
+ if (!this.dropDownWrapper.nativeElement.contains(event.target)) {
+ this.show = false;
+ }
+ }
+ }
+
+}
diff --git a/src/angular/form-elements/form-elements.module.ts b/src/angular/form-elements/form-elements.module.ts
new file mode 100644
index 0000000..744f8b8
--- /dev/null
+++ b/src/angular/form-elements/form-elements.module.ts
@@ -0,0 +1,38 @@
+import { NgModule } from "@angular/core";
+import { FormsModule, ReactiveFormsModule } from "@angular/forms";
+import { InputComponent } from "./input/input.component";
+import { DropDownComponent } from "./dropdown/dropdown.component";
+import { CommonModule } from "@angular/common";
+import { CheckboxComponent } from "./checkbox/checkbox.component";
+import { RadioGroupComponent } from "./radios/radio-buttons-group.component";
+import { AnimationDirectivesModule } from '../animations/animation-directives.module';
+import { DropDownTriggerDirective } from "./dropdown/dropdown-trigger.directive";
+import {SvgIconModule} from "../svg-icon/svg-icon.module";
+import { ValidationModule } from './validation/validation.module';
+
+@NgModule({
+ imports: [
+ CommonModule,
+ FormsModule,
+ ReactiveFormsModule,
+ AnimationDirectivesModule,
+ SvgIconModule
+ ],
+ declarations: [
+ DropDownComponent,
+ InputComponent,
+ CheckboxComponent,
+ RadioGroupComponent,
+ DropDownTriggerDirective,
+ ],
+ exports: [
+ DropDownComponent,
+ DropDownTriggerDirective,
+ InputComponent,
+ CheckboxComponent,
+ RadioGroupComponent,
+ ValidationModule
+ ]
+})
+export class FormElementsModule {
+}
diff --git a/src/angular/form-elements/input/input.component.html.ts b/src/angular/form-elements/input/input.component.html.ts
new file mode 100644
index 0000000..f8a4609
--- /dev/null
+++ b/src/angular/form-elements/input/input.component.html.ts
@@ -0,0 +1,19 @@
+export default `
+<div class="sdc-input ">
+ <label class="sdc-input__label" *ngIf="label" [ngClass]="{'required':required}">{{label}}</label>
+ <input
+ class="sdc-input__input {{classNames}}"
+ [ngClass]="{'error': !valid, 'disabled':disabled}"
+ [attr.name]="name ? name : null"
+ [placeholder]="placeHolder"
+ [(ngModel)]="value"
+ [maxlength]="maxLength"
+ [minlength]="minLength"
+ [type]="type"
+ [formControl]="control"
+ [attr.disabled]="disabled ? 'disabled' : null"
+ (input)="onKeyPress($event.target.value)"
+ [attr.data-tests-id]="testId"
+ />
+</div>
+`;
diff --git a/src/angular/form-elements/input/input.component.ts b/src/angular/form-elements/input/input.component.ts
new file mode 100644
index 0000000..af0e9f4
--- /dev/null
+++ b/src/angular/form-elements/input/input.component.ts
@@ -0,0 +1,54 @@
+import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
+import { FormControl } from "@angular/forms";
+import { ValidationComponent } from '../validation/validation.component';
+import { ValidatableComponent } from './../validation/validatable.component';
+import 'rxjs/add/operator/debounceTime';
+import template from "./input.component.html";
+
+@Component({
+ selector: 'sdc-input',
+ template: template,
+})
+export class InputComponent extends ValidatableComponent implements OnInit {
+
+ @Output('valueChange') public baseEmitter: EventEmitter<any> = new EventEmitter<any>();
+ @Input() public label: string;
+ @Input() public value: any;
+ @Input() public name: string;
+ @Input() public classNames: string;
+ @Input() public disabled: boolean;
+ @Input() public type: string;
+ @Input() public placeHolder: string;
+ @Input() public required: boolean;
+ @Input() public minLength: number;
+ @Input() public maxLength: number;
+ @Input() public debounceTime: number;
+ @Input() public testId: string;
+
+ protected control: FormControl;
+
+ constructor() {
+ super();
+ this.control = new FormControl('', []);
+ this.debounceTime = 0;
+ this.placeHolder = '';
+ this.type = 'text';
+ }
+
+ ngOnInit() {
+ this.control.valueChanges.
+ debounceTime(this.debounceTime)
+ .subscribe((newValue: any) => {
+ this.baseEmitter.emit(this.value);
+ });
+ }
+
+ public getValue(): any {
+ return this.value;
+ }
+
+ onKeyPress(value: string) {
+ this.valueChanged(this.value);
+ }
+
+}
diff --git a/src/angular/form-elements/radios/radio-button.model.ts b/src/angular/form-elements/radios/radio-button.model.ts
new file mode 100644
index 0000000..1ad4b3f
--- /dev/null
+++ b/src/angular/form-elements/radios/radio-button.model.ts
@@ -0,0 +1,15 @@
+export interface IRadioButtonModel {
+ label: string;
+ disabled: boolean;
+ name: string;
+ value: string;
+};
+
+export interface IOptionGroup {
+ items: IRadioButtonModel[];
+};
+
+export enum Direction {
+ vertical,
+ horizontal
+}
diff --git a/src/angular/form-elements/radios/radio-buttons-group.component.html.ts b/src/angular/form-elements/radios/radio-buttons-group.component.html.ts
new file mode 100644
index 0000000..28a27af
--- /dev/null
+++ b/src/angular/form-elements/radios/radio-buttons-group.component.html.ts
@@ -0,0 +1,20 @@
+export default `
+<label class='sdc-radio-group__legend'>{{legend}}</label>
+<div class='sdc-radio-group__radios {{direction}}'>
+ <template *ngFor="let item of options.items">
+ <div class="sdc-radio">
+ <label class="sdc-radio__animation-wrapper" SdcRippleClickAnimation [rippleClickDisabled]="disabled">
+ <input class="sdc-radio__input"
+ type="radio"
+ name="{{item.name}}"
+ value="{{item.value}}"
+ disabled="{{disabled || item.disabled || false}}"
+ (change)="onValueChanged(item.value)"
+ [(ngModel)]="value"
+ />
+ <span class="sdc-radio__label">{{ item.label }}</span>
+ </label>
+ </div>
+ </template>
+</div>
+`;
diff --git a/src/angular/form-elements/radios/radio-buttons-group.component.spec.ts b/src/angular/form-elements/radios/radio-buttons-group.component.spec.ts
new file mode 100644
index 0000000..273a701
--- /dev/null
+++ b/src/angular/form-elements/radios/radio-buttons-group.component.spec.ts
@@ -0,0 +1,52 @@
+import { TestBed, async } from '@angular/core/testing';
+import { RadioGroupComponent } from "./radio-buttons-group.component";
+import { FormsModule } from "@angular/forms";
+import { IRadioButtonModel } from "./radio-button.model";
+import { AnimationDirectivesModule } from "../../animations/animation-directives.module";
+
+
+describe("Radio Buttons unit-tests", ()=>{
+ let component: RadioGroupComponent;
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [
+ RadioGroupComponent
+ ],
+ imports:[
+ FormsModule,
+ AnimationDirectivesModule
+ ]
+ }).compileComponents();
+
+ const fixture = TestBed.createComponent(RadioGroupComponent);
+ component = fixture.componentInstance;
+ component.disabled = false;//TODO constructor
+ component.options = {
+ items: []
+ };
+ }));
+
+ it('Component Created', async(()=> {
+ expect(component).toBeDefined();
+ }));
+
+ it('Not possible to choose value which not exists', async(() =>{
+ component.value = 'test';
+ expect(component.value).not.toEqual('test');
+ }));
+
+ it('Normal flow', async(() =>{
+ component.options.items = [ <IRadioButtonModel> {
+ value: 'val1',
+ name: 'exp6',
+ label: 'Label of Radio1'
+ }, <IRadioButtonModel> {
+ value: 'val2',
+ name: 'exp6',
+ label: 'Label of Radio2'
+ }];
+ component.value = component.options.items[0].value;
+ expect(component.value).toEqual(component.options.items[0].value);
+ }));
+
+});
diff --git a/src/angular/form-elements/radios/radio-buttons-group.component.ts b/src/angular/form-elements/radios/radio-buttons-group.component.ts
new file mode 100644
index 0000000..800d8b0
--- /dev/null
+++ b/src/angular/form-elements/radios/radio-buttons-group.component.ts
@@ -0,0 +1,52 @@
+import { Component, Input, Output, ViewEncapsulation, EventEmitter, HostBinding } from "@angular/core";
+import { Direction, IOptionGroup, IRadioButtonModel } from "./radio-button.model";
+import template from './radio-buttons-group.component.html';
+
+@Component({
+ selector: 'sdc-radio-group',
+ template: template,
+ encapsulation: ViewEncapsulation.None
+})
+export class RadioGroupComponent {
+
+ private _direction: Direction = Direction.vertical;
+ private _selectedValue: string;
+
+ @HostBinding('class') classes = 'sdc-radio-group';
+
+ @Input() public legend: string;
+ @Input() public options: IOptionGroup;
+ @Input() public disabled: boolean;
+
+ @Input()
+ get value(): string {
+ return this._selectedValue;
+ }
+ set value(value: string) {
+ if (this.isOptionExists(value)) {
+ this._selectedValue = value;
+ }
+ }
+
+ @Output() public valueChange: EventEmitter<string> = new EventEmitter<string>();
+
+ @Input()
+ get direction(): string {
+ return Direction[this._direction];
+ }
+ set direction(direction: string) {
+ this._direction = (direction === 'horizontal' ? Direction.horizontal : Direction.vertical);
+ }
+
+ public onValueChanged(value): void {
+ this.valueChange.emit(value);
+ }
+
+ private isOptionExists(value) {
+ const exist = this.options.items.find((item: IRadioButtonModel) => {
+ return item.value === value;
+ });
+ return exist !== undefined;
+ }
+
+}
diff --git a/src/angular/form-elements/validation/validatable.component.ts b/src/angular/form-elements/validation/validatable.component.ts
new file mode 100644
index 0000000..4817dea
--- /dev/null
+++ b/src/angular/form-elements/validation/validatable.component.ts
@@ -0,0 +1,25 @@
+import { Input, Component } from "@angular/core";
+import { ValidationComponent } from './validation.component';
+import { Subject } from 'rxjs/Subject';
+import { IValidatableComponent } from './validatable.interface';
+
+export abstract class ValidatableComponent implements IValidatableComponent {
+
+ // Each ValidatableComponent should handle the style in case of error, according to this boolean
+ public valid = true;
+
+ // Each ValidatableComponent will notify when the value is changed.
+ public notifier: Subject<string>;
+
+ constructor() {
+ this.notifier = new Subject();
+ }
+
+ public abstract getValue(): any;
+
+ // Each ValidatableComponent should call the valueChanged on value changed function.
+ protected valueChanged = (value: any): void => {
+ this.notifier.next(value);
+ }
+
+}
diff --git a/src/angular/form-elements/validation/validatable.interface.ts b/src/angular/form-elements/validation/validatable.interface.ts
new file mode 100644
index 0000000..6aceafe
--- /dev/null
+++ b/src/angular/form-elements/validation/validatable.interface.ts
@@ -0,0 +1,5 @@
+export interface IValidatableComponent {
+
+ getValue(): any;
+
+}
diff --git a/src/angular/form-elements/validation/validation-group.component.html.ts b/src/angular/form-elements/validation/validation-group.component.html.ts
new file mode 100644
index 0000000..dff591e
--- /dev/null
+++ b/src/angular/form-elements/validation/validation-group.component.html.ts
@@ -0,0 +1,3 @@
+export default `
+<ng-content></ng-content>
+`;
diff --git a/src/angular/form-elements/validation/validation-group.component.ts b/src/angular/form-elements/validation/validation-group.component.ts
new file mode 100644
index 0000000..59ecf4c
--- /dev/null
+++ b/src/angular/form-elements/validation/validation-group.component.ts
@@ -0,0 +1,47 @@
+import { Input, Component, ContentChildren, EventEmitter, Output, QueryList, SimpleChanges, HostBinding, AfterContentInit } from "@angular/core";
+import { AbstractControl, FormControl } from "@angular/forms";
+import { Subscribable } from "rxjs/Observable";
+import { AnonymousSubscription } from "rxjs/Subscription";
+import { IValidator } from './validators/validator.interface';
+import { ValidatorComponent } from './validators/base.validator.component';
+import { RegexValidatorComponent } from './validators/regex.validator.component';
+import { RequiredValidatorComponent } from './validators/required.validator.component';
+import { ValidatableComponent } from './validatable.component';
+import { ValidationComponent } from './validation.component';
+import { CustomValidatorComponent } from './validators/custom.validator.component';
+import template from "./validation.component.html";
+
+@Component({
+ selector: 'sdc-validation-group',
+ template
+})
+export class ValidationGroupComponent implements AfterContentInit {
+
+ @Input() public disabled: boolean;
+ @HostBinding('class') classes;
+
+ @ContentChildren(ValidationComponent) public validationsComponents: QueryList<ValidationComponent>;
+
+ private supportedValidator: Array<QueryList<ValidatorComponent>>;
+
+ constructor() {
+ this.disabled = false;
+ this.classes = 'sdc-validation-group';
+ }
+
+ ngAfterContentInit(): void {
+
+ }
+
+ public validate(): boolean {
+ let validationResult = true;
+ // Iterate over all validationComponent inside the group and return boolean result true in case all validations passed.
+ this.validationsComponents.forEach((validationComponent) => {
+ if (validationComponent.validate()) {
+ validationResult = false;
+ }
+ });
+ return validationResult;
+ }
+
+}
diff --git a/src/angular/form-elements/validation/validation.component.html.ts b/src/angular/form-elements/validation/validation.component.html.ts
new file mode 100644
index 0000000..0f11a23
--- /dev/null
+++ b/src/angular/form-elements/validation/validation.component.html.ts
@@ -0,0 +1,3 @@
+export default `
+<ng-content *ngIf="!disabled"></ng-content>
+`;
diff --git a/src/angular/form-elements/validation/validation.component.ts b/src/angular/form-elements/validation/validation.component.ts
new file mode 100644
index 0000000..4abdd12
--- /dev/null
+++ b/src/angular/form-elements/validation/validation.component.ts
@@ -0,0 +1,79 @@
+import { Input, Component, ContentChildren, EventEmitter, Output, QueryList, SimpleChanges, HostBinding, AfterContentInit } from "@angular/core";
+import { AbstractControl, FormControl } from "@angular/forms";
+import { Subscribable } from "rxjs/Observable";
+import { AnonymousSubscription } from "rxjs/Subscription";
+import { IValidator } from './validators/validator.interface';
+import { ValidatorComponent } from './validators/base.validator.component';
+import { RegexValidatorComponent } from './validators/regex.validator.component';
+import { RequiredValidatorComponent } from './validators/required.validator.component';
+import { ValidatableComponent } from './validatable.component';
+import { CustomValidatorComponent } from './validators/custom.validator.component';
+import template from "./validation.component.html";
+
+@Component({
+ selector: 'sdc-validation',
+ template
+})
+export class ValidationComponent implements AfterContentInit {
+
+ @Input() public validateElement: ValidatableComponent;
+ @Input() public disabled: boolean;
+ @Output() public validityChanged: EventEmitter<boolean> = new EventEmitter<boolean>();
+ @HostBinding('class') classes;
+
+ // @ContentChildren does not recieve type any or IValidator or ValidatorComponent, so need to create @ContentChildren for each validator type.
+ @ContentChildren(RegexValidatorComponent) public regexValidator: QueryList<ValidatorComponent>;
+ @ContentChildren(RequiredValidatorComponent) public requireValidator: QueryList<ValidatorComponent>;
+ @ContentChildren(CustomValidatorComponent) public customValidator: QueryList<ValidatorComponent>;
+
+ private supportedValidator: Array<QueryList<ValidatorComponent>>;
+
+ constructor() {
+ this.disabled = false;
+ this.classes = 'sdc-validation';
+ }
+
+ ngAfterContentInit(): void {
+ this.supportedValidator = [
+ this.regexValidator,
+ this.requireValidator,
+ this.customValidator
+ ];
+
+ this.validateElement.notifier.subscribe(
+ (value) => {
+ const validationResult = this.validateOnChange(value);
+ this.validateElement.valid = validationResult;
+ },
+ (error) => console.log('Validation subscribe error')
+ );
+ }
+
+ public validate = (): boolean => {
+ const value = this.validateElement.getValue();
+ return this.validateOnChange(value);
+ }
+
+ private validateOnChange(value: any): boolean {
+ if (this.disabled) { return true; }
+
+ /**
+ * Iterate over all validators types (required, regex, etc...), and inside each iterate over
+ * all validators with same type, and return boolean result true in case all validations passed.
+ */
+ const validationResult: boolean = this.supportedValidator.reduce((sum, validatorName) => {
+ const response: boolean = validatorName.reduce((_sum, validator) => {
+ return _sum && validator.validate(value);
+ }, true);
+ return sum && response;
+ }, true);
+
+ if (this.validateElement.valid !== validationResult) {
+ this.validityChanged.emit(validationResult);
+ }
+
+ return validationResult;
+
+ }
+
+}
diff --git a/src/angular/form-elements/validation/validation.module.ts b/src/angular/form-elements/validation/validation.module.ts
new file mode 100644
index 0000000..4213f76
--- /dev/null
+++ b/src/angular/form-elements/validation/validation.module.ts
@@ -0,0 +1,35 @@
+import { NgModule } from "@angular/core";
+import { FormsModule, ReactiveFormsModule } from "@angular/forms";
+import { CommonModule } from "@angular/common";
+import { SvgIconModule } from './../../svg-icon/svg-icon.module';
+import { ValidationComponent } from './validation.component';
+import { ValidatorComponent } from './validators/base.validator.component';
+import { RequiredValidatorComponent } from './validators/required.validator.component';
+import { RegexValidatorComponent } from './validators/regex.validator.component';
+import { CustomValidatorComponent } from './validators/custom.validator.component';
+import { ValidationGroupComponent } from './validation-group.component';
+
+@NgModule({
+ imports: [
+ FormsModule,
+ CommonModule,
+ ReactiveFormsModule,
+ SvgIconModule
+ ],
+ declarations: [
+ ValidationComponent,
+ RegexValidatorComponent,
+ RequiredValidatorComponent,
+ CustomValidatorComponent,
+ ValidationGroupComponent
+ ],
+ exports: [
+ ValidationComponent,
+ RegexValidatorComponent,
+ RequiredValidatorComponent,
+ CustomValidatorComponent,
+ ValidationGroupComponent
+ ]
+})
+export class ValidationModule {
+}
diff --git a/src/angular/form-elements/validation/validators/base.validator.component.html.ts b/src/angular/form-elements/validation/validators/base.validator.component.html.ts
new file mode 100644
index 0000000..aba8eed
--- /dev/null
+++ b/src/angular/form-elements/validation/validators/base.validator.component.html.ts
@@ -0,0 +1,10 @@
+export default `
+<svg-icon-label
+ *ngIf="!isValid"
+ name="alert-triangle"
+ mode="error"
+ size="small"
+ [label]="message"
+ labelPlacement="right">
+</svg-icon-label>
+`;
diff --git a/src/angular/form-elements/validation/validators/base.validator.component.ts b/src/angular/form-elements/validation/validators/base.validator.component.ts
new file mode 100644
index 0000000..3d751af
--- /dev/null
+++ b/src/angular/form-elements/validation/validators/base.validator.component.ts
@@ -0,0 +1,25 @@
+import { Input, Component, ContentChildren, EventEmitter, Output, QueryList, SimpleChanges, HostBinding } from "@angular/core";
+import { IValidator } from './validator.interface';
+import template from "./base.validator.component.html";
+
+@Component({
+ selector: 'sdc-validator',
+ template: template
+})
+export abstract class ValidatorComponent {
+
+ @Input() public message: any;
+ @Input() public disabled: boolean;
+ @HostBinding('class') classes;
+
+ protected isValid: boolean;
+
+ constructor() {
+ this.disabled = false;
+ this.isValid = true;
+ this.classes = 'sdc-validator sdc-label__error';
+ }
+
+ public abstract validate(value: any): boolean;
+
+}
diff --git a/src/angular/form-elements/validation/validators/custom.validator.component.ts b/src/angular/form-elements/validation/validators/custom.validator.component.ts
new file mode 100644
index 0000000..eb09636
--- /dev/null
+++ b/src/angular/form-elements/validation/validators/custom.validator.component.ts
@@ -0,0 +1,23 @@
+import { Input, Component } from "@angular/core";
+import { ValidatorComponent } from "./base.validator.component";
+import { IValidator } from './validator.interface';
+import template from "./base.validator.component.html";
+
+@Component({
+ selector: 'sdc-custom-validator',
+ template: template
+})
+export class CustomValidatorComponent extends ValidatorComponent implements IValidator {
+
+ @Input() public callback: (...args) => boolean;
+
+ constructor() {
+ super();
+ }
+
+ public validate(value: any): boolean {
+ this.isValid = this.callback(value);
+ return this.isValid;
+ }
+
+}
diff --git a/src/angular/form-elements/validation/validators/regex.validator.component.ts b/src/angular/form-elements/validation/validators/regex.validator.component.ts
new file mode 100644
index 0000000..5929016
--- /dev/null
+++ b/src/angular/form-elements/validation/validators/regex.validator.component.ts
@@ -0,0 +1,24 @@
+import { Input, Component } from "@angular/core";
+import { ValidatorComponent } from "./base.validator.component";
+import { IValidator } from './validator.interface';
+import template from "./base.validator.component.html";
+
+@Component({
+ selector: 'sdc-regex-validator',
+ template: template
+})
+export class RegexValidatorComponent extends ValidatorComponent implements IValidator {
+
+ @Input() public pattern: RegExp;
+
+ constructor() {
+ super();
+ }
+
+ public validate(value: any): boolean {
+ const regexp = new RegExp(this.pattern);
+ this.isValid = regexp.test(value);
+ return this.isValid;
+ }
+
+}
diff --git a/src/angular/form-elements/validation/validators/required.validator.component.ts b/src/angular/form-elements/validation/validators/required.validator.component.ts
new file mode 100644
index 0000000..7eee932
--- /dev/null
+++ b/src/angular/form-elements/validation/validators/required.validator.component.ts
@@ -0,0 +1,25 @@
+import { Input, Component } from "@angular/core";
+import { ValidatorComponent } from "./base.validator.component";
+import { IValidator } from './validator.interface';
+import template from "./base.validator.component.html";
+
+@Component({
+ selector: 'sdc-required-validator',
+ template: template
+})
+export class RequiredValidatorComponent extends ValidatorComponent implements IValidator {
+
+ constructor() {
+ super();
+ }
+
+ public validate(value: any): boolean {
+ if (value) {
+ this.isValid = true;
+ } else {
+ this.isValid = false;
+ }
+ return this.isValid;
+ }
+
+}
diff --git a/src/angular/form-elements/validation/validators/validator.interface.ts b/src/angular/form-elements/validation/validators/validator.interface.ts
new file mode 100644
index 0000000..c0adc24
--- /dev/null
+++ b/src/angular/form-elements/validation/validators/validator.interface.ts
@@ -0,0 +1,3 @@
+export interface IValidator {
+ validate(value: any): void;
+}