@ -1,10 +0,0 @@ | |||||
import { Component, Input } from '@angular/core'; | |||||
@Component({ | |||||
selector: 'appc-page-heading', | |||||
template: `<h4>{{text}}</h4>` | |||||
}) | |||||
export class PageHeadingComponent { | |||||
@Input() text: string; | |||||
constructor() { } | |||||
} |
@ -1,41 +0,0 @@ | |||||
import { | |||||
fakeAsync, | |||||
tick, | |||||
TestBed | |||||
} from '@angular/core/testing'; | |||||
import { Component } from '@angular/core'; | |||||
import { By } from '@angular/platform-browser/src/dom/debug/by'; | |||||
// Load the implementations that should be tested | |||||
import { XLargeDirective } from './x-large.directive'; | |||||
describe('x-large directive', () => { | |||||
// Create a test component to test directives | |||||
@Component({ | |||||
template: '<div x-large>Content</div>' | |||||
}) | |||||
class TestComponent { } | |||||
beforeEach(() => { | |||||
TestBed.configureTestingModule({ | |||||
declarations: [ | |||||
XLargeDirective, | |||||
TestComponent | |||||
] | |||||
}); | |||||
}); | |||||
it('should sent font-size to x-large', fakeAsync(() => { | |||||
TestBed.compileComponents().then(() => { | |||||
const fixture = TestBed.createComponent(TestComponent); | |||||
fixture.detectChanges(); | |||||
tick(); | |||||
const element = fixture.debugElement.query(By.css('div')); | |||||
// expect(element.nativeElement.style.fontSize).toBe('x-large'); | |||||
}); | |||||
})); | |||||
}); |
@ -1,18 +0,0 @@ | |||||
import { Directive, ElementRef, Renderer } from '@angular/core'; | |||||
/* | |||||
* Directive | |||||
* XLarge is a simple directive to show how one is made | |||||
*/ | |||||
@Directive({ | |||||
selector: '[appdXlarge]' // using [ ] means selecting attributes | |||||
}) | |||||
export class XLargeDirective { | |||||
constructor(element: ElementRef, renderer: Renderer) { | |||||
// simple DOM manipulation to set font size to x-large | |||||
// `nativeElement` is the direct reference to the DOM element | |||||
// element.nativeElement.style.fontSize = 'x-large'; | |||||
// for server/webworker support use the renderer | |||||
renderer.setElementStyle(element.nativeElement, 'fontSize', 'x-large'); | |||||
} | |||||
} |
@ -1,36 +0,0 @@ | |||||
export class ControlBase<T>{ | |||||
value: T; | |||||
key: string; | |||||
label: string; | |||||
placeholder: string; | |||||
required: boolean; | |||||
minlength: number; | |||||
maxlength: number; | |||||
order: number; | |||||
type: string; | |||||
class: string; | |||||
constructor(options: { | |||||
value?: T, | |||||
key?: string, | |||||
label?: string, | |||||
placeholder?: string, | |||||
required?: boolean, | |||||
minlength?: number, | |||||
maxlength?: number, | |||||
order?: number, | |||||
type?: string, | |||||
class?: string; | |||||
} = {}) { | |||||
this.value = options.value; | |||||
this.key = options.key || ''; | |||||
this.label = options.label || ''; | |||||
this.placeholder = options.placeholder || ''; | |||||
this.required = !!options.required; | |||||
this.minlength = options.minlength; | |||||
this.maxlength = options.maxlength; | |||||
this.order = options.order === undefined ? 1 : options.order; | |||||
this.type = options.type || ''; | |||||
this.class = options.class || ''; | |||||
} | |||||
} |
@ -1,11 +0,0 @@ | |||||
import { ControlBase } from './control-base'; | |||||
export class ControlCheckbox extends ControlBase<string> { | |||||
type: string; | |||||
constructor(options: any = {}) { | |||||
super(options); | |||||
this.type = 'checkbox'; | |||||
this.value = options.value || false; | |||||
} | |||||
} |
@ -1,11 +0,0 @@ | |||||
import { ControlBase } from './control-base'; | |||||
export class ControlDropdown extends ControlBase<string> { | |||||
options: { key: string, value: string }[] = []; | |||||
constructor(options: any = {}) { | |||||
super(options); | |||||
this.type = 'dropdown'; | |||||
this.options = options.options || []; | |||||
} | |||||
} |
@ -1,8 +0,0 @@ | |||||
import { ControlBase } from './control-base'; | |||||
export class ControlTextbox extends ControlBase<boolean> { | |||||
constructor(options: any = {}) { | |||||
super(options); | |||||
this.type = options.type || 'textbox'; | |||||
} | |||||
} |
@ -1,26 +0,0 @@ | |||||
<!--{{f.controls[control.key] | json}}--> | |||||
<div #f="ngForm" [formGroup]="form" [ngSwitch]="control.type" class="form-group {{control.class}}" [class.has-danger]="invalid" | |||||
[class.has-success]="valid" [class.form-check]="control.type === 'checkbox'"> | |||||
<label *ngSwitchCase="'dropdown'" [attr.for]="control.key" class="col-form-label">{{control.label}}</label> | |||||
<select *ngSwitchCase="'dropdown'" [id]="control.key" [formControlName]="control.key" [class.form-control-success]="valid" | |||||
[class.form-control-danger]="invalid" class="form-control"> | |||||
<option *ngFor="let opt of control.options" [value]="opt.key">{{opt.value}}</option> | |||||
</select> | |||||
<label *ngSwitchCase="'checkbox'" [attr.for]="control.key" class="form-check-label"> | |||||
<input #ck *ngSwitchCase="'checkbox'" (change)="control.value = ck.checked" [id]="control.key" [formControlName]="control.key" | |||||
[type]="control.type" class="form-check-input"> | |||||
{{control.label}} | |||||
</label> | |||||
<!--This is not the switch case because of multiple control types--> | |||||
<label *ngIf="control.type === 'textbox' || control.type === 'email' || control.type === 'password'" [attr.for]="control.key" class="col-form-label">{{control.label}}</label> | |||||
<input *ngIf="control.type === 'textbox' || control.type === 'email' || control.type === 'password'" [id]="control.key" [formControlName]="control.key" [type]="control.type" | |||||
[placeholder]="control.placeholder" [class.form-control-success]="valid" [class.form-control-danger]="invalid" class="form-control"> | |||||
<appc-control-error-message [form]="f" [control]="control"></appc-control-error-message> | |||||
</div> |
@ -1,26 +0,0 @@ | |||||
import { Component, Input } from '@angular/core'; | |||||
import { FormGroup } from '@angular/forms'; | |||||
import { ControlBase } from './control-base'; | |||||
import { ErrorMessageComponent } from './error-message.component'; | |||||
@Component({ | |||||
selector: 'appc-dynamic-control', | |||||
templateUrl: './dynamic-form-control.component.html' | |||||
}) | |||||
export class DynamicFormControlComponent { | |||||
@Input() control; | |||||
@Input() form; | |||||
constructor() { | |||||
this.control = undefined; | |||||
this.form = undefined; | |||||
} | |||||
get valid() { | |||||
return this.form.controls[this.control.key].valid; | |||||
} | |||||
get invalid() { | |||||
return !this.form.controls[this.control.key].valid && this.form.controls[this.control.key].touched; | |||||
} | |||||
} |
@ -1,5 +0,0 @@ | |||||
<form class="{{formClass}}" (ngSubmit)="onSubmit()" [formGroup]="form" novalidate role="form"> | |||||
<appc-dynamic-control *ngFor="let ctrl of controls" [control]="ctrl" [form]="form"></appc-dynamic-control> | |||||
<button type="submit" class="btn btn-primary pull-right" [disabled]="!form.valid">{{btnText}}</button> | |||||
</form> |
@ -1,30 +0,0 @@ | |||||
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core'; | |||||
import { FormGroup } from '@angular/forms'; | |||||
import { ControlBase } from './control-base'; | |||||
import { FormControlService } from './form-control.service'; | |||||
@Component({ | |||||
selector: 'appc-dynamic-form', | |||||
templateUrl: './dynamic-form.component.html' | |||||
}) | |||||
export class DynamicFormComponent implements OnInit { | |||||
@Input() controls: ControlBase<any>[] = []; | |||||
@Input() btnText: string = 'Submit'; // Default value at least | |||||
@Input() formClass: string = 'form-horizontal'; | |||||
// Note: don't keep name of output events as same as native events such as submit etc. | |||||
@Output() formsubmit: EventEmitter<any> = new EventEmitter<any>(); | |||||
form: FormGroup; | |||||
constructor(private _controlService: FormControlService) { } | |||||
ngOnInit() { | |||||
let sortedControls = this.controls.sort((a, b) => a.order - b.order); | |||||
this.form = this._controlService.toControlGroup(sortedControls); | |||||
} | |||||
onSubmit() { | |||||
this.formsubmit.emit(this.form.value); | |||||
} | |||||
} |
@ -1,25 +0,0 @@ | |||||
import { Component, Host, Input } from '@angular/core'; | |||||
import { FormGroupDirective } from '@angular/forms'; | |||||
import { ControlBase } from './control-base'; | |||||
import { ValidationService } from './validation.service'; | |||||
@Component({ | |||||
selector: 'appc-control-error-message', | |||||
template: `<div *ngIf="errorMessage" class="form-control-feedback"> {{errorMessage}} </div>` | |||||
}) | |||||
export class ErrorMessageComponent { | |||||
@Input() control: ControlBase<any>; | |||||
@Input() form: FormGroupDirective; | |||||
constructor() { } | |||||
get errorMessage() { | |||||
let c = this.form.form.get(this.control.key); | |||||
for (let propertyName in c.errors) { | |||||
if (c.errors.hasOwnProperty(propertyName) && c.touched) { | |||||
return ValidationService.getValidatorErrorMessage(propertyName, this.control.minlength || this.control.maxlength); | |||||
} | |||||
} | |||||
return undefined; | |||||
} | |||||
} |
@ -1,7 +0,0 @@ | |||||
<div class="alert alert-danger" *ngIf="errors?.length > 0"> | |||||
<ul> | |||||
<li *ngFor="let error of errors"> | |||||
{{error}} | |||||
</li> | |||||
</ul> | |||||
</div> |
@ -1,11 +0,0 @@ | |||||
import { Component, Input } from '@angular/core'; | |||||
@Component({ | |||||
selector: 'appc-error-summary', | |||||
templateUrl: './error-summary.component.html' | |||||
}) | |||||
export class ErrorSummaryComponent { | |||||
@Input() errors: string | string[]; | |||||
constructor() { } | |||||
} |
@ -1,41 +0,0 @@ | |||||
import { Injectable } from '@angular/core'; | |||||
import { FormControl, FormGroup, Validators } from '@angular/forms'; | |||||
import { ControlBase } from './control-base'; | |||||
import { ValidationService } from './validation.service'; | |||||
@Injectable() | |||||
export class FormControlService { | |||||
constructor() { } | |||||
toControlGroup(controls: ControlBase<any>[]) { | |||||
let group: any = {}; | |||||
controls.forEach(control => { | |||||
let validators = []; | |||||
// Required | |||||
if (control.required) { | |||||
validators.push(Validators.required); | |||||
} | |||||
// Minlength | |||||
if (control.minlength) { | |||||
validators.push(Validators.minLength(control.minlength)); | |||||
} | |||||
// Maxlength | |||||
if (control.maxlength) { | |||||
validators.push(Validators.minLength(control.maxlength)); | |||||
} | |||||
if (control.type === 'email') { | |||||
validators.push(ValidationService.emailValidator); | |||||
} | |||||
// Password | |||||
if (control.type === 'password') { | |||||
validators.push(ValidationService.passwordValidator); | |||||
} | |||||
group[control.key] = new FormControl(control.value || '', validators); | |||||
}); | |||||
return new FormGroup(group); | |||||
} | |||||
} |
@ -1,42 +0,0 @@ | |||||
export class ValidationService { | |||||
static getValidatorErrorMessage(code: string, fieldLength: number) { | |||||
let config: any = { | |||||
'required': 'This is a required field', | |||||
'minlength': 'Minimum length is ' + fieldLength, | |||||
'maxlength': 'Maximum length is ' + fieldLength, | |||||
'invalidCreditCard': 'Invalid credit card number', | |||||
'invalidEmailAddress': 'Invalid email address', | |||||
'invalidPassword': 'Password must be at least 6 characters long, and contain a number and special character.' | |||||
}; | |||||
return config[code]; | |||||
} | |||||
static creditCardValidator(control: any) { | |||||
// Visa, MasterCard, American Express, Diners Club, Discover, JCB | |||||
if (control.value.match(/^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11})$/)) { | |||||
return undefined; | |||||
} else { | |||||
return { 'invalidCreditCard': true }; | |||||
} | |||||
} | |||||
static emailValidator(control: any) { | |||||
// RFC 2822 compliant regex | |||||
if (control.value.match(/[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/)) { | |||||
return undefined; | |||||
} else { | |||||
return { 'invalidEmailAddress': true }; | |||||
} | |||||
} | |||||
static passwordValidator(control: any) { | |||||
// {6,100} - Assert password is between 6 and 100 characters | |||||
// (?=.*[0-9]) - Assert a string has at least one number | |||||
if (control.value.match(/^(?=.*[0-9])[a-zA-Z0-9!"@#$%^&*]{6,100}$/)) { | |||||
return undefined; | |||||
} else { | |||||
return { 'invalidPassword': true }; | |||||
} | |||||
} | |||||
} |
@ -1,25 +0,0 @@ | |||||
import { Injectable } from '@angular/core'; | |||||
import { Router } from '@angular/router'; | |||||
@Injectable() | |||||
export class UtilityService { | |||||
private _router: Router; | |||||
constructor(router: Router) { | |||||
this._router = router; | |||||
} | |||||
convertDateTime(date: Date) { | |||||
let _formattedDate = new Date(date.toString()); | |||||
return _formattedDate.toDateString(); | |||||
} | |||||
navigate(path: string) { | |||||
this._router.navigate([path]); | |||||
} | |||||
navigateToSignIn() { | |||||
this.navigate('/login'); | |||||
} | |||||
} |