소스 검색

Adding Hidden component, and minor alts to metadata generation

Richard Knight 6 년 전
부모
커밋
4ff8a055de

+ 1 - 0
src/app/dynaform/components/index.ts

@@ -7,6 +7,7 @@ export { TextareaComponent } from './native/textarea/textarea.component';
 export { PasswordComponent } from './native/password/password.component';
 export { SelectComponent } from './native/select/select.component';
 export { RadioComponent } from './native/radio/radio.component';
+export { HiddenComponent } from './native/hidden/hidden.component';
 export { DropdownModifiedInputComponent } from './custom/dropdown-modified-input/dropdown-modified-input.component';
 export { CheckbuttonComponent } from './custom/checkbutton/checkbutton.component';
 export { CheckbuttonGroupComponent } from './group/checkbutton-group/checkbutton-group.component';

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

@@ -0,0 +1 @@
+<input type="hidden" [formControl]="control">

+ 0 - 0
src/app/dynaform/components/native/hidden/hidden.component.scss


+ 25 - 0
src/app/dynaform/components/native/hidden/hidden.component.spec.ts

@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { HiddenComponent } from './hidden.component';
+
+describe('HiddenComponent', () => {
+  let component: HiddenComponent;
+  let fixture: ComponentFixture<HiddenComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ HiddenComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(HiddenComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 11 - 0
src/app/dynaform/components/native/hidden/hidden.component.ts

@@ -0,0 +1,11 @@
+import { Component } from '@angular/core';
+import { NativeInputComponent } from '../../_abstract/native-input.component';
+
+@Component({
+	selector: 'app-hidden',
+	templateUrl: './hidden.component.html',
+	styleUrls: ['./hidden.component.scss']
+})
+export class HiddenComponent extends NativeInputComponent {
+
+}

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

@@ -8,7 +8,7 @@
 <ng-template #default let-control="control" let-meta="meta">
 	
 	<div class="form-group row" *ngIf="meta.type !== 'Container'; else recursiveDynaform" [ngClass]="getRowClass(control, meta.type)">
-		<label class="col-sm-4" [for]="meta.name">
+		<label class="col-sm-4" [for]="meta.name" title="">
 			{{ meta.label }}
 			<span *ngIf="control && control.touched && control.invalid" class="fas fa-exclamation-triangle"
 				[ngbTooltip]="getValidationFailureMessage(control, meta)"

+ 21 - 6
src/app/dynaform/dynaform.component.ts

@@ -48,7 +48,7 @@ export class DynaformComponent implements OnInit {
 	template?: TemplateRef<any>;
 
 	@Input()
-	debug = false;
+	debug = true;
 
 	@Output()
 	call: EventEmitter<string> = new EventEmitter<string>();
@@ -58,6 +58,10 @@ export class DynaformComponent implements OnInit {
 	dynaFormRows: string[];
 	path: string[]; // path of current FormGroup - can be used to respond differently based on nesting level in template
 
+	// Colours for CSS in console
+	conRed = 'color: white; background-color: maroon; font-weight: bold;';
+	conGreen = 'color: white; background-color: green; font-weight: bold;';
+
 	constructor(
 		@Optional() private cc: ControlContainer,
 	) {}
@@ -65,16 +69,22 @@ export class DynaformComponent implements OnInit {
 	ngOnInit() {
 		// Get the formGroup from the formGroupName if necessary
 		if (!this.formGroup && this.formGroupName) {
-			this.formGroup = <FormGroup>this.cc.control; // Get theFormGroup from the ControlContainer
+			this.formGroup = <FormGroup>this.cc.control; // Get theFormGroup from the inhected ControlContainer
 		}
 		if (!this.formGroup) {
 			throw new Error('Dynaform Component initialised without [formGroup] or formGroupName');
 		}
+		if (typeof this.formMetaData !== 'object') {
+			throw new Error('Dynaform: [meta] should be an object');
+		}
 		this.controlNames = Object.keys(this.formGroup.controls);
 		this.path = this.getFormGroupPath();
+		if (this.debug && this.path.length < 2) {
+			this.displayDebuggingInConsole();
+		}
 
 		// If we're given a formGroupName or nested FormGroup, and the form's full (or partial but fuller) metadata tree,
-		// drill down to find the FormGroup's metadata
+		// drill down to find *this* FormGroup's metadata
 		const path = [...this.path]; // Clone to avoid mutating this.path
 		const metaDataKeysExpected = this.controlNames.join(',');
 		while (path.length && metaDataKeysExpected !== this.getContolKeysCSVFromMetadata(this.formMetaData)) {
@@ -84,8 +94,6 @@ export class DynaformComponent implements OnInit {
 		this.dynaFormRows = Object.keys(this.formMetaData);
 
 		// Check we've got a "FormGroup <---> MetaData" match
-		//
-		// const metaDataKeys = Object.keys(this.formMetaData).join(','); // OLD VERSION - before we introduced entities like ButtonGroup that don't create FormControls
 		const metaDataKeysFound = this.getContolKeysCSVFromMetadata(this.formMetaData);
 		if (metaDataKeysFound !== metaDataKeysExpected) {
 			throw new Error(`
@@ -148,7 +156,7 @@ export class DynaformComponent implements OnInit {
 	}
 
 	private getContolKeysCSVFromMetadata(metadata): string {
-		// Return CSV of control keys references in nesting-levels metadata,
+		// Return CSV of control keys in current nesting-level's metadata,
 		// excluding metadata points that don't create FormControls, FromGroups or FormArrays
 		// (identified by their 'noFormControsl' flag)
 		// e.g. ButtonGroups, HTMLChunks, etc.
@@ -158,4 +166,11 @@ export class DynaformComponent implements OnInit {
 				.join(',');
 	}
 
+	private displayDebuggingInConsole(): void {
+		console.log('%c *** MetaData *** ', this.conGreen);
+		console.dir(this.formMetaData);
+		console.log('%c *** FormGroup *** ', this.conGreen);
+		console.dir(this.formGroup);
+	}
+
 }

+ 4 - 1
src/app/dynaform/models/field.model.ts

@@ -47,7 +47,9 @@ interface TimePickerFieldMetaData extends SimpleFieldMetaData {
 }
 
 // Utility to unCamelCase
-const unCamelCase = (str: string): string => str.replace(/([A-Z])/g, ' $1').replace(/^./, s => s.toUpperCase());
+const unCamelCase = (str: string): string => str.replace(/([A-Z])/g, ' $1')
+												.replace(/(\w)(\d)/g, '$1 $2')
+												.replace(/^./, s => s.toUpperCase());
 
 // ---------------------------------------------------------------------------------------------------------------------
 // Form Field MetaData Models
@@ -213,6 +215,7 @@ class Container {
 	template?: TemplateRef<any>;
 	meta: StringMap; // TODO: Tighten up on type with union type
 	constructor(meta: StringMap) {
+		console.log(meta);
 		meta.meta ? Object.assign(this, meta) : this.meta = meta;
 		if (!this.label) {
 			this.label = unCamelCase(this.name);

+ 7 - 3
src/app/dynaform/services/_formdata-utils.ts

@@ -31,7 +31,7 @@ interface AbstractControlOptions {
 
 
 // ---------------------------------------------------------------------------------------------------------------------
-// AutoMeta: Generate Autimatic Metadata from a raw model (or mapped raw model)
+// AutoMeta: Generate Automatic Metadata from a raw model (or mapped raw model)
 // ---------------------------------------------------------------------------------------------------------------------
 
 const isScalar = val => typeof val === 'boolean' || typeof val === 'number' || typeof val === 'string';
@@ -39,13 +39,17 @@ const isArray = val => Array.isArray(val);
 
 const keyValPairToMeta = (val, key) => ({ name: key, [isScalar(val) ? 'value' : 'meta']: val });
 const keyValPairToMetaRecursive = ( [key, val] ) => {
+	if (val === null || val === undefined) {
+		val = ''; // Treat null or undefined as empty string, for purpose of form
+	}
 	const innerVal = isScalar(val) ? val : (isArray(val) ? arrayToMeta(val) : autoMeta(val));
 	const metaVal = keyValPairToMeta(innerVal, key);
 	return [key, metaVal];
 };
-const arrayToMeta = array => array.map(val => ({ name: val, 'value' : val }));
+const arrayMemberAutoName = val => isScalar(val) ? val : val.__typename || val.constructor.name;
+const arrayToMeta = array => array.map(val => ({ name: arrayMemberAutoName(val), 'value' : val }));
 
-const autoMeta = model => Object.entries(model)
+const autoMeta = model =>  Object.entries(model)
 	.map(keyValPairToMetaRecursive)
 	.reduce((res, [key, val]) => addProp(res, key, val), {});
 

+ 3 - 0
src/app/dynaform/services/model-mapper.service.ts

@@ -152,6 +152,9 @@ export class ModelMapperService {
 	
 	public reverseMap = (model: StringMap, mapping: StringMap = this.mapping, mapMissing = false, pathsToDelete = [], stack = []): StringMap => {
 		// pathToDelete contains a list of source paths to delete from the model, leaving the missing to be straight-mapped
+		if (!mapping) {
+			throw new Error('Attempting to use Model Mapper without mapping');
+		}
 		let res = {};
 		let modelClone = stack.length ? model : _.cloneDeep(model); // Clone the model unless inside a recursive call
 		Object.keys(mapping).forEach(key => { 

+ 1 - 1
src/styles.scss

@@ -1,6 +1,6 @@
 /* You can add global styles to this file, and also import other style files */
 
-@import url("https://use.fontawesome.com/releases/v5.0.6/css/all.css");
+@import url("https://use.fontawesome.com/releases/v5.1.0/css/all.css");
 @import "~@progress/kendo-theme-bootstrap/scss/all";
 
 h1 {