Sfoglia il codice sorgente

Investigating subscriptions and event emitters

Richard Knight 6 anni fa
parent
commit
74fa02585e

+ 5 - 0
package-lock.json

@@ -648,6 +648,11 @@
         }
       }
     },
+    "angular-super-validator": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/angular-super-validator/-/angular-super-validator-2.0.0.tgz",
+      "integrity": "sha512-FPC6rjSiunK88Wdu5Dbr/XbOQDiIat87XJVa7ScGbQnjEBASeZS/FhxNgWgq6dp/A0YIKNRpRBTddB4Qq6ftig=="
+    },
     "ansi-html": {
       "version": "0.0.7",
       "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz",

+ 1 - 0
package.json

@@ -26,6 +26,7 @@
     "@progress/kendo-angular-intl": "^1.4.1",
     "@progress/kendo-angular-l10n": "^1.2.0",
     "@progress/kendo-theme-bootstrap": "^2.13.5",
+    "angular-super-validator": "^2.0.0",
     "core-js": "^2.4.1",
     "json-formatter-js": "^2.2.0",
     "lodash": "^4.17.10",

+ 14 - 4
src/app/_mock/testfields.v6.ts

@@ -3,12 +3,15 @@
 import { Validators } from '@angular/forms';
 
 const model = {
+	field: '',
+	field2: '',
 	dynaformtest: {
-		a: 'Value 1',
+		a: '',
+		valTest: '',
 		b: 'Value 2',
 		c: 'Maybe',
 		d: {
-			e: 444,
+			e: '',
 			f: 555,
 			g: {
 				h: true,
@@ -22,12 +25,19 @@ const model = {
 const meta = {
 	dynaformtest: {
 		meta: {
-			a: { validators: [ Validators.required, Validators.minLength(4) ] },
+			valTest: {
+				validators: [ Validators.required, Validators.minLength(4) ],
+				// perhaps have some standard messages for standard failures in the Object.prototype ??
+				valFailureMessages: {
+					'required': 'This field is required',
+					'minlength': 'Please type something longer'
+				}
+			},
 			b: { type: 'checkbutton' },
 			c: { label: 'Property Three', type: 'radio', options: ['Yes', 'No', 'Maybe'], horizontal: 1 },
 			d: {
 				meta: {
-					e: { type: 'radio', 'label': 'Does it work yet?' },
+					e: { type: 'radio', 'label': 'Does it work yet?', validators: Validators.required },
 					f: { type: 'datepicker' }
 				}
 			}

+ 2 - 3
src/app/app.component.html

@@ -10,15 +10,14 @@
 	<form [formGroup]="form">
 		<span formGroupName="dynaformtest">
 			<span formGroupName="d">
-				<app-dynaform [formGroup]="form" [meta]="meta" [template]="tref"></app-dynaform>
+				<app-dynaform [formGroup]="form" [meta]="meta" [template]="tref" debug="1"></app-dynaform>
 				<!-- <app-dynaform formGroupName="g" [meta]="meta" [template]="tref"></app-dynaform> -->
 			</span>
 		</span>
 	</form>
 	<div calss="row">
 		<div class="col-12 pt-4 pb-4">
-			<!-- <json-formatter [data]="form.value" open="4"></json-formatter> -->
-			<json-formatter [data]="form.errors" open="4"></json-formatter>
+			<json-formatter [data]="form.value" open="4"></json-formatter>
 		</div>
 	</div>
 </div>

+ 1 - 1
src/app/dynaform/components/_abstract/native-input.component.ts

@@ -1,5 +1,5 @@
 import { Input, OnInit } from '@angular/core';
-import { FormControl, FormGroup } from '@angular/forms';
+import { FormControl } from '@angular/forms';
 
 export abstract class NativeInputComponent implements OnInit {
 

+ 0 - 1
src/app/dynaform/components/native/text/text.component.html

@@ -3,4 +3,3 @@
 	[placeholder]="placeholder"
 	class="form-control form-control-sm"
 >
-

+ 38 - 5
src/app/dynaform/directives/dynafield.directive.ts

@@ -1,8 +1,13 @@
 import {
 	Directive, ComponentFactoryResolver, ComponentRef, ViewContainerRef,
-	Input, Output, EventEmitter, OnInit, SkipSelf
+	Input, Output, EventEmitter, OnInit, OnDestroy,
+	Optional, Self, SkipSelf, Inject
 } from '@angular/core';
-import { Form, FormControl, ControlContainer, NgControl, ControlValueAccessor, ValidatorFn, AsyncValidatorFn } from '@angular/forms';
+import {
+	Form, FormControl, AbstractControl,
+	NgControl, ControlContainer, ControlValueAccessor, 
+	NG_VALIDATORS, Validator, Validators, ValidatorFn, AsyncValidatorFn
+} from '@angular/forms';
 
 import * as formFieldComponents from './../components';
 
@@ -17,7 +22,7 @@ type FFCCustom = FFC & ControlValueAccessor;
 	// tslint:disable-next-line:directive-selector
 	selector: '[dynafield]'
 })
-export class DynafieldDirective extends NgControl implements OnInit {
+export class DynafieldDirective extends NgControl implements OnInit, OnDestroy {
 
 	@Input()
 	meta: StringMap;
@@ -41,7 +46,8 @@ export class DynafieldDirective extends NgControl implements OnInit {
 	constructor(
 		private resolver: ComponentFactoryResolver,
 		private container: ViewContainerRef,
-		@SkipSelf() private cc: ControlContainer
+		@SkipSelf() private cc: ControlContainer,
+		@Optional() @Self() @Inject(NG_VALIDATORS) private validators: Array<Validator | ValidatorFn>
 	) {
 		super();
 	}
@@ -96,6 +102,13 @@ export class DynafieldDirective extends NgControl implements OnInit {
 				// so we need to wire it up!
 				this.name = name;
 				this.valueAccessor = <FFCCustom>this.component.instance;
+				/* Attaching Validators to Custom FormControls ... UNTESTED AS YET
+				const ngValidators = this.component.injector.get(NG_VALIDATORS, null);
+				console.log('ngValidators', ngValidators);
+				if (ngValidators && ngValidators.some(x => x as any === this.component.instance)) {
+					this.validators = [...(this.validators || []), ...(ngValidators as Array<Validator|ValidatorFn>)];
+				}
+				*/
 				this._control = this.formGroupDirective.addControl(this);
 			}
 		} catch (e) {
@@ -104,6 +117,15 @@ export class DynafieldDirective extends NgControl implements OnInit {
 		}
 	}
 
+    ngOnDestroy(): void {
+        if (this.formGroupDirective) {
+            this.formGroupDirective.removeControl(this);
+        }
+        if (this.component) {
+            this.component.destroy();
+        }
+    }
+
 	get path(): string[] {
 		return [...this.cc.path, this.name];
 	}
@@ -112,13 +134,24 @@ export class DynafieldDirective extends NgControl implements OnInit {
 		return this.cc ? this.cc.formDirective : null;
 	}
 
-	get validator(): ValidatorFn | null { return null; }
+	get validator(): ValidatorFn | null { 
+        return this.validators !== null ? Validators.compose(this.validators.map(this.normalizeValidator)) : null;
+	}
 
 	get asyncValidator(): AsyncValidatorFn { return null; }
 
+	// Override the method in NgControl
 	viewToModelUpdate(newValue: any): void {
 		this.update.emit(newValue);
 	}
 
+	normalizeValidator(validator: ValidatorFn | Validator): ValidatorFn {
+		if ((<Validator>validator).validate) {
+			return (c: AbstractControl) => (<Validator>validator).validate(c);
+		} else {
+			return <ValidatorFn>validator;
+		}
+	}
+	
 }
 

+ 10 - 1
src/app/dynaform/dynaform.component.html

@@ -18,10 +18,19 @@
 	
 	<ng-template #recursiveDynaform>
 		<div class="row pt-3">
-			<h3 class="col-sm-12">{{ meta.name }}</h3>
+			<h3 class="col-sm-12" [ngClass]="'h-dyna-' + (path.length + 2)">{{ meta.name }}</h3>
 		</div>
 		<app-dynaform [formGroup]="control" [meta]="meta.meta" [template]="template"></app-dynaform>
 	</ng-template>
 
 </ng-template>
 
+
+<pre *ngIf="debug && path.length === 0"
+	[ngClass]="{
+		'alert-success' 	: formGroup.valid && formGroup.dirty,
+		'alert-danger' 		: !formGroup.valid && formGroup.dirty,
+		'alert-secondary' 	: !formGroup.dirty
+	}"
+	class="alert mt-4"
+	role="alert">{{ formGroup.pristine ? 'Untouched' : getValidationErrors() | json }}</pre>

+ 27 - 3
src/app/dynaform/dynaform.component.ts

@@ -1,5 +1,7 @@
-import { Component, Input, TemplateRef, Optional, OnInit, ChangeDetectionStrategy } from '@angular/core';
+import { Component, Input, Output, TemplateRef, Optional, OnInit, ChangeDetectionStrategy } from '@angular/core';
 import { FormGroup, FormGroupName, AbstractControl, ControlContainer } from '@angular/forms';
+import { SuperForm } from 'angular-super-validator';
+import { EventEmitter } from 'events';
 
 export interface DynarowContext {
 	control: AbstractControl;
@@ -51,8 +53,15 @@ export class DynaformComponent implements OnInit {
 	@Input()
 	template?: TemplateRef<any>;
 
+	@Input()
+	debug? = false;
+	
+	@Output()
+	dynaval = new EventEmitter();
+
 	formMetaData: any; // TODO: Tighten up type
 	controlNames: string[];
+	path: string[]; // path of current FormGroup - can be used to respond differently based on nesting level in template
 
 	constructor(
 		@Optional() private cc: ControlContainer
@@ -67,17 +76,19 @@ export class DynaformComponent implements OnInit {
 			throw new Error('Dynaform Component initialised without [formGroup] or formGroupName');
 		}
 		this.controlNames = Object.keys(this.formGroup.controls);
+		this.path = this.getFormGroupPath();
+		// Object.freeze(this.path);
 
 		// If we're given a formGroupName or nested FormFroup, and the form's full (or partial but fuller) metadata tree,
 		// drill down to find the corresponding FormGroup's metadata
-		const path = this.getFormGroupPath();
+		const path = [...this.path]; // Clone to avoid mutating this.path
 		const metaDataKeysExpected = this.controlNames.join(',');
 		while (path.length && metaDataKeysExpected !== Object.keys(this.formMetaData).join(',')) {
 			const branch = path.pop();
 			this.formMetaData = this.formMetaData[branch].meta;
 		}
 		
-		// Check we've got a match
+		// Check we've got a "FormGroup <---> MetaData" match
 		const metaDataKeys = Object.keys(this.formMetaData).join(',');
 		if (metaDataKeys !== metaDataKeysExpected) {
 			throw new Error(`
@@ -119,4 +130,17 @@ export class DynaformComponent implements OnInit {
 		return `row-${inputType.toLowerCase().replace('component', '')}`;
 	}
 
+	getValidationErrors() {
+		if (!this.formGroup.valid) {
+			// const errors = SuperForm.getAllErrors(this.formGroup);
+			const errorsFlat = SuperForm.getAllErrorsFlat(this.formGroup);
+			return errorsFlat;		
+		}
+		return 'No Errors';
+	}
+
+	detected(val) {
+		console.log('DETECTED', val);
+	}
+
 }

+ 25 - 6
src/styles.scss

@@ -17,6 +17,7 @@ div.col-sm-8 {
 div.col-sm-4 {
 	background-color: lavenderblush;
 	border-top: 3px white solid;
+	border-left: 15px white solid;
 	padding-top: 4px;
 	margin-bottom: 3px;
 }
@@ -59,9 +60,27 @@ div.col-sm-8 {
 	max-width: 165px;
 }
 
-input[type=text].ng-invalid ,
-input[type=password].ng-invalid ,
-textarea.ng-invalid ,
-select.ng-invalid  {
-	border-left: 3px red solid;
-}
+.ng-touched.ng-invalid {
+	input[type=text]#{&},
+	input[type=password]#{&},
+	textarea#{&},
+	select#{&}
+	{
+		border-left: 3px red solid;
+	}
+}
+// ---------------------------------------------------------------------------------------------------------------------
+// FormGroup Heading Styles
+
+.h-dyna-2 {
+	font-size: 1.5em;
+}
+
+.h-dyna-3 {
+	font-size: 1.25em;
+}
+
+.h-dyna-4 {
+	font-size: 1.1em;
+}
+