From be7862ff4c2b62e9f57e7b00cd96dad480a90d91 Mon Sep 17 00:00:00 2001 From: ploutarchos Date: Thu, 11 May 2017 00:22:03 +0100 Subject: [PATCH 1/4] Implemented custom validation of the form control. This works with both ngModel forms and Reactive Forms. --- README.md | 3 +++ src/combo-box.component.ts | 44 +++++++++++++++++++++++++++++++++----- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index cf2e278..240da3f 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,9 @@ delay before triggering search. `inputClass: string = 'form-control';` class to apply to the inner input field +`inputErrorClass: string;` +class to append to the inner input field if the control becomes invalid + `loadingIconClass: string = 'loader';` class to apply to the loading icon element diff --git a/src/combo-box.component.ts b/src/combo-box.component.ts index eb5386c..5953351 100644 --- a/src/combo-box.component.ts +++ b/src/combo-box.component.ts @@ -1,23 +1,23 @@ import {Component, OnInit, Input, Output, EventEmitter, forwardRef, ViewChild} from '@angular/core'; import {Observable, Subscription} from 'rxjs/Rx'; -import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; +import {AbstractControl, ControlValueAccessor, NG_VALIDATORS, NG_VALUE_ACCESSOR, Validator} from '@angular/forms'; @Component({ moduleId: 'ng2-combobox', selector: 'combo-box', template: `
- - +
- +
ComboBoxComponent), multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => ComboBoxComponent), + multi: true }] }) -export class ComboBoxComponent implements ControlValueAccessor, OnInit { +export class ComboBoxComponent implements ControlValueAccessor, OnInit, Validator { @Input() displayField: string; @@ -117,6 +122,8 @@ export class ComboBoxComponent implements ControlValueAccessor, OnInit { @Input() inputClass: string = 'form-control'; @Input() + inputErrorClass: string; + @Input() loadingIconClass: string = 'loader'; @Input() triggerIconClass: string = 'trigger'; @@ -142,6 +149,7 @@ export class ComboBoxComponent implements ControlValueAccessor, OnInit { hideList: boolean = true; data: any[]; + valid: boolean = true; private _loading: boolean = false; private _listDataSubscription: Subscription; @@ -160,6 +168,10 @@ export class ComboBoxComponent implements ControlValueAccessor, OnInit { private propagateChange = (_: any) => { }; + // Validator props + private propagateValidate = (_: any) => { + }; + constructor() { } @@ -452,6 +464,8 @@ export class ComboBoxComponent implements ControlValueAccessor, OnInit { this._input.nativeElement.focus(); } + // Implement ControlValueAccessor interface + writeValue(value: any): void { value = this.searchValueObject(value); @@ -471,4 +485,24 @@ export class ComboBoxComponent implements ControlValueAccessor, OnInit { registerOnTouched(fn: any): void { this.propagateTouch = fn; } + + // Implement Validator interface + validate(c: AbstractControl): { [key: string]: any; } { + if(!this.remote) { + this.valid = !! this._initialData.find(user => this.getDisplayValue(user) === this.currVal); + } else { + // TODO implement validation for remote data + this.valid = true; + } + + if(!this.valid) { + return {'INVALID': 'Could not match the input to any of the provided data'}; + } else { + return null; + } + } + + registerOnValidatorChange(fn: () => void): void { + this.propagateValidate = fn; + } } From 54c22473c3e797d919a688bc6b21b267dd6e4af0 Mon Sep 17 00:00:00 2001 From: ploutarchos Date: Thu, 11 May 2017 01:05:22 +0100 Subject: [PATCH 2/4] Made the control invalid by default which is backwards compatible. --- src/combo-box.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/combo-box.component.ts b/src/combo-box.component.ts index 5953351..86b858f 100644 --- a/src/combo-box.component.ts +++ b/src/combo-box.component.ts @@ -149,7 +149,7 @@ export class ComboBoxComponent implements ControlValueAccessor, OnInit, Validato hideList: boolean = true; data: any[]; - valid: boolean = true; + valid: boolean = false; private _loading: boolean = false; private _listDataSubscription: Subscription; From 9a3e222c2183bf9c5480dfff98a9f4a9961e5f89 Mon Sep 17 00:00:00 2001 From: ploutarchos Date: Fri, 12 May 2017 23:21:11 +0100 Subject: [PATCH 3/4] Removed inputErrorClass @Input argument It is not needed since styling of invalid status can now be achieved from the ng-invalid class that angular adds to the wrapper html element when the component becomes invalid. --- README.md | 3 --- src/combo-box.component.ts | 4 +--- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/README.md b/README.md index 240da3f..cf2e278 100644 --- a/README.md +++ b/README.md @@ -58,9 +58,6 @@ delay before triggering search. `inputClass: string = 'form-control';` class to apply to the inner input field -`inputErrorClass: string;` -class to append to the inner input field if the control becomes invalid - `loadingIconClass: string = 'loader';` class to apply to the loading icon element diff --git a/src/combo-box.component.ts b/src/combo-box.component.ts index 86b858f..106b20a 100644 --- a/src/combo-box.component.ts +++ b/src/combo-box.component.ts @@ -7,7 +7,7 @@ import {AbstractControl, ControlValueAccessor, NG_VALIDATORS, NG_VALUE_ACCESSOR, selector: 'combo-box', template: `
- Date: Fri, 12 May 2017 23:58:43 +0100 Subject: [PATCH 4/4] Enforce validation check on start up - added extra validation error Always initialise the list data to an empty array to fix a console error. Changed combo box to enforce a validation check on start up after the list data has been populated from the host component. This will always make the control invalid on start up, but at the same time it will be pristine and untouched. Changed combo box to generate two different validation errors "empty_input" and "input_no_match". --- src/combo-box.component.ts | 53 +++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/src/combo-box.component.ts b/src/combo-box.component.ts index 106b20a..7664094 100644 --- a/src/combo-box.component.ts +++ b/src/combo-box.component.ts @@ -167,7 +167,7 @@ export class ComboBoxComponent implements ControlValueAccessor, OnInit, Validato }; // Validator props - private propagateValidate = (_: any) => { + private propagateValidate = () => { }; constructor() { @@ -197,9 +197,15 @@ export class ComboBoxComponent implements ControlValueAccessor, OnInit, Validato }); } else { let data = value; + if(!data) { + data = []; + } this.data = this._initialData = data; this.loading = false; } + + // During initialisation, the validation was not being triggered after setting the list data. + this.propagateValidate(); } @Input() @@ -447,15 +453,8 @@ export class ComboBoxComponent implements ControlValueAccessor, OnInit, Validato this.propagateChange(this.getValueValue(val)); } - private searchValueObject(value: any): any { - if (false === value instanceof Object && this.valueField && this._initialData) { - this._initialData.forEach((item) => { - if (value === this.getValueValue(item)) { - value = item; - } - }); - } - return value; + private searchByDisplayValue(displayValue: any): any { + return this._initialData.find(item => this.getDisplayValue(item) === displayValue); } onTriggerClick() { @@ -465,13 +464,11 @@ export class ComboBoxComponent implements ControlValueAccessor, OnInit, Validato // Implement ControlValueAccessor interface writeValue(value: any): void { - value = this.searchValueObject(value); - if (value instanceof Object && this.getDisplayValue(value)) { - this.currVal = this.getDisplayValue(value); - } else { - this._tmpVal = value; - } + this.currVal = this.getDisplayValue(value); + this._tmpVal = this.getValueValue(value); + + this.propagateTouch(); this.onInitValue.emit(value); } @@ -486,18 +483,22 @@ export class ComboBoxComponent implements ControlValueAccessor, OnInit, Validato // Implement Validator interface validate(c: AbstractControl): { [key: string]: any; } { - if(!this.remote) { - this.valid = !! this._initialData.find(user => this.getDisplayValue(user) === this.currVal); - } else { - // TODO implement validation for remote data - this.valid = true; - } - if(!this.valid) { - return {'INVALID': 'Could not match the input to any of the provided data'}; - } else { - return null; + if (!this.remote && this._initialData && this._initialData.length > 0) { + + if(!this.currVal || !this.currVal.trim()) { + return {'empty_input': 'Input value is empty'}; + } else { + let isValid = !! this.searchByDisplayValue(this.currVal); + if(!isValid) { + return {'input_no_match': `Cannot match the input value '${this.currVal}' to any of the provided data`}; + } + } } + + // TODO implement validation for remote data + + return null; } registerOnValidatorChange(fn: () => void): void {