Browse Source

Early support for component type mutation

Richard Knight 4 years ago
parent
commit
f02a4e8088

+ 1 - 1
README.md

@@ -234,7 +234,7 @@ e.g. radio, horizontal.
           address1: {},
           address1: {},
           address2: {},
           address2: {},
           city: {},
           city: {},
-          postcode: { class: 'short-field' }
+          postcode: { class: 'multicolor' }
         }
         }
       }
       }
 		}
 		}

+ 2 - 2
src/app/_mock/testfields.v11.ts

@@ -7,11 +7,11 @@ const model = {};
 const meta = {
 const meta = {
 	container: {
 	container: {
 		label: 'Fields should inherit seeded meta from the container',
 		label: 'Fields should inherit seeded meta from the container',
-		seed: { class: 'short-field', value: 5 },
+		seed: { class: 'multicolor', value: 5 },
 		meta: {
 		meta: {
 			a: {},
 			a: {},
 			b: {},
 			b: {},
-			c: { value: 6, class: 'short-field' }, 
+			c: { value: 6, class: 'multicolor' }, 
 			d: {},
 			d: {},
 			nestedContiner: {
 			nestedContiner: {
 				label: 'More deeply nested container',
 				label: 'More deeply nested container',

+ 1 - 1
src/app/_mock/testfields.v12.ts

@@ -23,7 +23,7 @@ const model = {
 const meta = {
 const meta = {
 	repeating: {
 	repeating: {
 		label: 'Repeating Group',
 		label: 'Repeating Group',
-		seed: { class: 'short-field' },
+		seed: { class: 'multicolor' },
 		minRepeat: 1,
 		minRepeat: 1,
 		maxRepeat: 5,
 		maxRepeat: 5,
 		initialRepeat: 3,
 		initialRepeat: 3,

+ 1 - 1
src/app/_mock/testfields.v13.ts

@@ -19,7 +19,7 @@ const model = {
 const meta = {
 const meta = {
 	repeating: {
 	repeating: {
 		label: 'Repeating Group',
 		label: 'Repeating Group',
-		seed: { class: 'short-field' },
+		seed: { class: 'multicolor' },
 		minRepeat: 1,
 		minRepeat: 1,
 		maxRepeat: 5,
 		maxRepeat: 5,
 		initialRepeat: 3,
 		initialRepeat: 3,

+ 1 - 1
src/app/_mock/testfields.v14.ts

@@ -19,7 +19,7 @@ const model = {
 const meta = {
 const meta = {
 	repeating: {
 	repeating: {
 		label: 'Repeating Group',
 		label: 'Repeating Group',
-		seed: { class: 'short-field' },
+		seed: { class: 'multicolor' },
 		minRepeat: 1,
 		minRepeat: 1,
 		maxRepeat: 5,
 		maxRepeat: 5,
 		initialRepeat: 3,
 		initialRepeat: 3,

+ 27 - 52
src/app/_mock/testfields.v17.ts

@@ -1,5 +1,5 @@
 // ---------------------------------------------------------------------------------------------------------------------
 // ---------------------------------------------------------------------------------------------------------------------
-// TESTS: container groups? again
+// TESTS: Modification of Meta
 // ---------------------------------------------------------------------------------------------------------------------
 // ---------------------------------------------------------------------------------------------------------------------
 
 
 import { Validators as V } from '@angular/forms';
 import { Validators as V } from '@angular/forms';
@@ -7,57 +7,32 @@ import { Validators as V } from '@angular/forms';
 const model = {};
 const model = {};
 
 
 const meta = {
 const meta = {
-	general: {
-		label: 'Genaral Details',
-		source: '/',
-		meta: {
-			name: { validators: V.required },
-			description: { type: 'textarea' },
-			duration: { },
-			unit: { label: 'Time Unit' }
-		}
-	},
-	email: {
-		label: 'Email Configuration',
-		source: '/',
-		meta: {
-			emailTo: { label: 'To', validators: [ V.required, V.email ] },
-			emailCc: { 
-				minRepeat: 1,
-				maxRepeat: 5,
-				initialRepeat: 1,
-				showAddControl: true,
-				showDeleteControl: true,
-				validators: V.email
-			},
-			emailBcc: { 
-				minRepeat: 1,
-				maxRepeat: 5,
-				initialRepeat: 1,
-				showAddControl: true,
-				showDeleteControl: true,
-				validators: V.email
-			},
-			subject: { validators: V.required },
-			message: { type: 'textarea' }
-		}
-	},
-	report: {
-		label: 'Report Configuration',
-		source: '/',
-		meta: {
-			table: {
-				type: 'select',
-				options: [ 'AAA', 'BBB' ],
-				validators: V.required
-			},
-			columns: {
-				source: '/',
-				type: 'checkboxGroup',
-				meta: ['Ab', 'Cd', 'Ef', 'Ghi']
-			}
-		}
+	testField: {
+		type: 'text',
+		placeholder: 'Does it work?'
+		// meta: ['Ab', 'Cd', 'Ef', 'Ghi']
 	}
 	}
 };
 };
 
 
-export { model, meta };
+/*
+const meta2 = {
+	testField: {
+		type: 'text',
+		placeholder: 'WOO HOO!'
+		// meta: ['Ab', 'Cd', 'Ef', 'Ghi']
+	}
+}
+*/
+
+const meta2 = {
+	testField: {
+		type: 'select',
+		placeholder: 'WOO HOO!',
+		class: 'multicolor',
+		options: ['WOO HOO!', 'Yabba Dabba Doo!']
+		// meta: ['Ab', 'Cd', 'Ef', 'Ghi']
+	}
+}
+
+export { model, meta, meta2 };
+

+ 17 - 0
src/app/_mock/testfields.v18.ts

@@ -0,0 +1,17 @@
+// ---------------------------------------------------------------------------------------------------------------------
+// TESTS: Modification of CheckBoxGroups
+// ---------------------------------------------------------------------------------------------------------------------
+
+import { Validators as V } from '@angular/forms';
+
+const model = {};
+
+const meta = {
+	columns: {
+		source: '/',
+		type: 'checkboxGroup',
+		meta: ['Ab', 'Cd', 'Ef', 'Ghi']
+	}
+};
+
+export { model, meta };

+ 13 - 1
src/app/app.component.ts

@@ -49,7 +49,8 @@ export class AppComponent implements OnInit, OnChanges {
 
 
 		// Optionally supply the test to run in the query string e.g. ?test=3
 		// Optionally supply the test to run in the query string e.g. ?test=3
 		const testcase = parseInt(new URLSearchParams(document.location.search).get('test'), 10) || defatltTest;
 		const testcase = parseInt(new URLSearchParams(document.location.search).get('test'), 10) || defatltTest;
-		const { model, meta } = testdata[testcase - 1];
+		// @ts-ignore: meta2 does not always exist
+		const { model, meta, meta2 } = testdata[testcase - 1];
 
 
 		console.log('%c *** TEST DATA *** ', this.hCssRed);
 		console.log('%c *** TEST DATA *** ', this.hCssRed);
 		console.log('Model', model);
 		console.log('Model', model);
@@ -93,6 +94,10 @@ export class AppComponent implements OnInit, OnChanges {
 			'SAYHELLO': this.sayHello,
 			'SAYHELLO': this.sayHello,
 			'SAYCHEESE': this.sayCheese
 			'SAYCHEESE': this.sayCheese
 		}, this);
 		}, this);
+
+		if (testcase >= 17) {
+			setTimeout(() => this.changeMeta(meta2), 2000);
+		}
 	}
 	}
 
 
 	ngOnChanges() {
 	ngOnChanges() {
@@ -110,5 +115,12 @@ export class AppComponent implements OnInit, OnChanges {
 	sayCheese() {
 	sayCheese() {
 		alert('CHEESE');
 		alert('CHEESE');
 	}
 	}
+	
+		
+	changeMeta(newMeta) {
+		const dynaformdata = this.dynaform.build({}, newMeta, true);
+		const { form, meta } = dynaformdata;
+		this.meta = meta;
+	}
 }
 }
 
 

+ 1 - 1
src/app/dynaform/components/custom/multiline/multiline.component.html

@@ -7,4 +7,4 @@
 		(blur)="updateValue()"
 		(blur)="updateValue()"
 	>
 	>
 </ng-container>
 </ng-container>
-<clr-control-error>{{ getFirstFailureMsg() }}</clr-control-error>
+<clr-control-error>{{ getFirstFailureMsg() }}</clr-control-error>

+ 32 - 3
src/app/dynaform/directives/dynafield.directive.ts

@@ -68,6 +68,7 @@ export class DynafieldDirective extends NgControl implements OnInit, OnChanges,
 	call: EventEmitter<string> = new EventEmitter<string>();
 	call: EventEmitter<string> = new EventEmitter<string>();
 
 
 	component: ComponentRef<IFFC|FFCCustom>;
 	component: ComponentRef<IFFC|FFCCustom>;
+	type: string;
 	_control;
 	_control;
 
 
 	constructor(
 	constructor(
@@ -82,6 +83,7 @@ export class DynafieldDirective extends NgControl implements OnInit, OnChanges,
 
 
 	ngOnInit() {
 	ngOnInit() {
 		const type = componentType(this.meta.type);
 		const type = componentType(this.meta.type);
+		this.type = type;
 		if (!formFieldComponents[type]) {
 		if (!formFieldComponents[type]) {
 			const validComponentTypes = Object.keys(formFieldComponents).join(', ');
 			const validComponentTypes = Object.keys(formFieldComponents).join(', ');
 			throw new Error(
 			throw new Error(
@@ -145,14 +147,37 @@ export class DynafieldDirective extends NgControl implements OnInit, OnChanges,
 	}
 	}
 
 
 	ngOnChanges() {
 	ngOnChanges() {
-		// We won't support mutating components (e.g. Text --> Select) at this stage,
+		// NOW UNDERWAY (see below): We won't support mutating components (e.g. Text --> Select) at this stage,
 		// but will support mutating an instantiated components metadata
 		// but will support mutating an instantiated components metadata
 		// As the component is created in ngOnInt this does nothing in the run before ngOnInit, but responds to later input changes
 		// As the component is created in ngOnInt this does nothing in the run before ngOnInit, but responds to later input changes
 		if (this.component) {
 		if (this.component) {
-			const { type, class: cssClass, id: cssId } = this.meta;
+			let { control, meta } = this;
+			const type = componentType(meta.type);
+			const { class: cssClass, id: cssId, disabled } = meta;
+			
+			// EARLY SUPPORT FOR COMPONENT TYPE MUTATION! REFACTOR into shared functions to avoid repitition with code in ngOnInit
+			if (type !== this.type) {
+				console.log('TYPE CHANGED', type);
+				this.type = type;
+				this.container.remove(0);
+				const componentFactory = this.resolver.resolveComponentFactory<IFFC>(formFieldComponents[type]);
+				this.component = this.container.createComponent(componentFactory);
+				const instance = this.component.instance;
+
+				// Check whether it's disabled, then set its FormControl and metadata
+				if (disabled) {
+					this.control.reset({ value: this.control.value, disabled: true });
+				}
+
+				// TODO: Change Update Strategy if necessary
+
+				instance.control = control;
+				instance.meta = meta;
+			}
+
 			this.setCssId(cssId);
 			this.setCssId(cssId);
 			this.setCssClasses(type, cssClass);
 			this.setCssClasses(type, cssClass);
-			this.component.instance.meta = this.meta;
+			this.component.instance.meta = meta;
 		}
 		}
 	}
 	}
 
 
@@ -165,6 +190,10 @@ export class DynafieldDirective extends NgControl implements OnInit, OnChanges,
 		}
 		}
 	}
 	}
 
 
+	insertComponent() {
+		
+	}
+
 	// ---------------------------------------
 	// ---------------------------------------
 	// Override methods / getters in NgControl
 	// Override methods / getters in NgControl
 
 

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

@@ -17,13 +17,13 @@
 		<ng-template #recursiveDynaform>
 		<ng-template #recursiveDynaform>
 			<ng-container [ngSwitch]="meta.type">
 			<ng-container [ngSwitch]="meta.type">
 
 
-				<div *ngSwitchCase="'RepeatingField'" class="dyna-rf-container">
+				<div *ngSwitchCase="'RepeatingField'" class="dyna-rf-container" [ngClass]="getEmbeddedDynaformClasses(meta)">
 					<div *ngFor="let field of meta.meta; let i = index" class="dyna-rf-field">
 					<div *ngFor="let field of meta.meta; let i = index" class="dyna-rf-field">
 						<button *ngIf="meta.showDeleteControl"
 						<button *ngIf="meta.showDeleteControl"
 							class="dyna-rep-btn-delete"
 							class="dyna-rep-btn-delete"
 							[disabled]="!deleteAllowed(meta.name)"
 							[disabled]="!deleteAllowed(meta.name)"
 							(click)="deleteRepeatingMember(meta.name, i)">
 							(click)="deleteRepeatingMember(meta.name, i)">
-							<clr-icon shape="trash"></clr-icon>
+							<clr-icon shape="trash"></clr-icon> {{ meta.deleteControlLabel }}
 						</button>
 						</button>
 						<ng-container *ngTemplateOutlet="dynafield; context: getRepeatingTemplateContext(meta.name, i)"></ng-container>
 						<ng-container *ngTemplateOutlet="dynafield; context: getRepeatingTemplateContext(meta.name, i)"></ng-container>
 					</div>
 					</div>
@@ -32,12 +32,12 @@
 							class="btn btn-sm btn-icon btn-outline-success dyna-rep-btn-add"
 							class="btn btn-sm btn-icon btn-outline-success dyna-rep-btn-add"
 							[disabled]="!addAllowed(meta.name)"
 							[disabled]="!addAllowed(meta.name)"
 							(click)="addRepeatingFieldMember(meta.name)">
 							(click)="addRepeatingFieldMember(meta.name)">
-							<clr-icon shape="plus"></clr-icon>
+							<clr-icon shape="plus"></clr-icon> {{ meta.addControlLabel }}
 						</button>
 						</button>
 					</div>
 					</div>
 				</div>
 				</div>
 	
 	
-				<ng-container *ngSwitchCase="'RepeatingContainer'">
+				<div *ngSwitchCase="'RepeatingContainer'" class="dyna-rc-container" [ngClass]="getEmbeddedDynaformClasses(meta)">
 					<div *ngIf="meta.display === 'SINGLE'" class="clr-row dyna-rc-selector">
 					<div *ngIf="meta.display === 'SINGLE'" class="clr-row dyna-rc-selector">
 						<div class="clr-col-sm-2 text-right dyna-rc-focus-block">
 						<div class="clr-col-sm-2 text-right dyna-rc-focus-block">
 							<b>FOCUS &gt;</b>
 							<b>FOCUS &gt;</b>
@@ -53,17 +53,17 @@
 								class="btn btn-sm btn-icon btn-outline-success dyna-rep-btn-add"
 								class="btn btn-sm btn-icon btn-outline-success dyna-rep-btn-add"
 								[disabled]="!addAllowed(meta.name)"
 								[disabled]="!addAllowed(meta.name)"
 								(click)="addRepeatingContainerMember(meta.name)">
 								(click)="addRepeatingContainerMember(meta.name)">
-								<clr-icon shape="plus"></clr-icon>
+								<clr-icon shape="plus"></clr-icon> {{ meta.addControlLabel }}
 							</button>
 							</button>
 						</div>
 						</div>
 					</div>
 					</div>
-					<div *ngFor="let container of meta.meta; let i = index" class="dyna-rc-container" [ngClass]="{ 'dyna-rc-display-all': meta.display === 'ALL' }">
+					<div *ngFor="let container of meta.meta; let i = index" class="dyna-rc-member-container" [ngClass]="{ 'dyna-rc-display-all': meta.display === 'ALL' }">
 						<button *ngIf="meta.showDeleteControl"
 						<button *ngIf="meta.showDeleteControl"
 							class="btn btn-sm btn-icon btn-outline-danger dyna-rep-btn-delete"
 							class="btn btn-sm btn-icon btn-outline-danger dyna-rep-btn-delete"
-							[ngClass]="{ 'dyna-hidden': !meta.meta[i].focussed }"
+							[ngClass]="{ 'dyna-hidden': meta.display === 'SINGLE' && !meta.meta[i].focussed }"
 							[disabled]="!deleteAllowed(meta.name)"
 							[disabled]="!deleteAllowed(meta.name)"
 							(click)="deleteRepeatingMember(meta.name, i)">
 							(click)="deleteRepeatingMember(meta.name, i)">
-							<clr-icon shape="trash"></clr-icon>
+							<clr-icon shape="trash"></clr-icon> {{ meta.deleteControlLabel }}
 						</button>
 						</button>
 						<ng-container *ngTemplateOutlet="dynaform; context: getRepeatingTemplateContext(meta.name, i)"></ng-container>
 						<ng-container *ngTemplateOutlet="dynaform; context: getRepeatingTemplateContext(meta.name, i)"></ng-container>
 					</div>
 					</div>
@@ -72,10 +72,10 @@
 							class="btn btn-sm btn-icon btn-outline-success dyna-rep-btn-add"
 							class="btn btn-sm btn-icon btn-outline-success dyna-rep-btn-add"
 							[disabled]="!addAllowed(meta.name)"
 							[disabled]="!addAllowed(meta.name)"
 							(click)="addRepeatingContainerMember(meta.name)">
 							(click)="addRepeatingContainerMember(meta.name)">
-							<clr-icon shape="plus"></clr-icon>
+							<clr-icon shape="plus"></clr-icon> {{ meta.addControlLabel }}
 						</button>
 						</button>
 					</div>
 					</div>
-				</ng-container>
+				</div>
 
 
 				<ng-container *ngSwitchCase="'Container'">
 				<ng-container *ngSwitchCase="'Container'">
 					<ng-container *ngTemplateOutlet="dynaform; context: { control: control, meta: meta }"></ng-container>
 					<ng-container *ngTemplateOutlet="dynaform; context: { control: control, meta: meta }"></ng-container>

+ 9 - 7
src/app/dynaform/dynaform.component.ts

@@ -65,6 +65,7 @@ export class DynaformComponent implements OnInit, OnChanges {
 	// Colours for CSS in console
 	// Colours for CSS in console
 	conRed = 'color: white; background-color: maroon; font-weight: bold;';
 	conRed = 'color: white; background-color: maroon; font-weight: bold;';
 	conGreen = 'color: white; background-color: green; font-weight: bold;';
 	conGreen = 'color: white; background-color: green; font-weight: bold;';
+	conOlive = 'color: white; background-color: olive; font-weight: bold;';
 
 
 	constructor(
 	constructor(
 		@Optional() private cc: ControlContainer
 		@Optional() private cc: ControlContainer
@@ -76,13 +77,14 @@ export class DynaformComponent implements OnInit, OnChanges {
 
 
 	ngOnChanges() {
 	ngOnChanges() {
 		// Triggered when inputs change
 		// Triggered when inputs change
-		// console.log('Dynaform ngOnChanges');
+		console.log('%c *** DynaformChange *** ', this.conOlive);
+		console.log(this.formMetaData);
 		// Get the formGroup from the formGroupName if necessary
 		// Get the formGroup from the formGroupName if necessary
 		if (!this.formGroup && this.formGroupName) {
 		if (!this.formGroup && this.formGroupName) {
 			this.formGroup = this.cc.control as FormGroup; // Get theFormGroup from the injected ControlContainer
 			this.formGroup = this.cc.control as FormGroup; // Get theFormGroup from the injected ControlContainer
 		}
 		}
 		if (!this.formGroup) {
 		if (!this.formGroup) {
-			throw new Error('Dynaform Component initialised without [formGroup] or formGroupName');
+			throw new Error(`Dynaform Component initialised without [formGroup] or cant't find formGroup from formGroupName ${this.formGroupName}`);
 		}
 		}
 		if (typeof this.formMetaData !== 'object') {
 		if (typeof this.formMetaData !== 'object') {
 			throw new Error('Dynaform: [meta] should be an object');
 			throw new Error('Dynaform: [meta] should be an object');
@@ -132,8 +134,8 @@ export class DynaformComponent implements OnInit, OnChanges {
 		return path;
 		return path;
 	}
 	}
 
 
-	getEmbeddedDynaformClasses(meta): StringMap<boolean> {
-		let ngClassObj = { 'dyna-hidden': !meta.focussed };
+	getEmbeddedDynaformClasses(meta: StringMap<any>): StringMap<boolean> {
+		let ngClassObj = {};
 		if (Array.isArray(meta.class)) {
 		if (Array.isArray(meta.class)) {
 			ngClassObj = (meta.class as string[]).reduce((acc, className) => (acc[className] = true, acc), ngClassObj);
 			ngClassObj = (meta.class as string[]).reduce((acc, className) => (acc[className] = true, acc), ngClassObj);
 		} else if (typeof meta.class === 'string' && meta.class) {
 		} else if (typeof meta.class === 'string' && meta.class) {
@@ -205,7 +207,7 @@ export class DynaformComponent implements OnInit, OnChanges {
 		}
 		}
 		// (2) Add metadata for new container member
 		// (2) Add metadata for new container member
 		const rfMeta = this.formMetaData[name];
 		const rfMeta = this.formMetaData[name];
-		const fieldTemplate = cloneDeep(rfMeta.__fieldTemplate);
+		const fieldTemplate = cloneDeep(rfMeta.__template);
 		const i = this.formMetaData[name].meta.length;
 		const i = this.formMetaData[name].meta.length;
 		fieldTemplate.name = `group${i+1}`; // CHECK
 		fieldTemplate.name = `group${i+1}`; // CHECK
 		rfMeta.meta.push(fieldTemplate);
 		rfMeta.meta.push(fieldTemplate);
@@ -222,7 +224,7 @@ export class DynaformComponent implements OnInit, OnChanges {
 		}
 		}
 		// (2) Add metadata for new container member
 		// (2) Add metadata for new container member
 		const rcMeta = this.formMetaData[name];
 		const rcMeta = this.formMetaData[name];
-		const containerTemplate = cloneDeep(rcMeta.__containerTemplate);
+		const containerTemplate = cloneDeep(rcMeta.__template);
 		const i = this.formMetaData[name].meta.length;
 		const i = this.formMetaData[name].meta.length;
 		containerTemplate.name = `group${i+1}`;
 		containerTemplate.name = `group${i+1}`;
 		rcMeta.meta.push(containerTemplate);
 		rcMeta.meta.push(containerTemplate);
@@ -281,7 +283,7 @@ export class DynaformComponent implements OnInit, OnChanges {
 		this.call.emit(fnId);
 		this.call.emit(fnId);
 	}
 	}
 
 
-	private getContolKeysCSVFromMetadata(metadata): string {
+	private getContolKeysCSVFromMetadata(metadata: StringMap<any>): string {
 		// Return CSV of control keys in current nesting-level's metadata,
 		// Return CSV of control keys in current nesting-level's metadata,
 		// excluding metadata points that don't create FormControls, FromGroups or FormArrays
 		// excluding metadata points that don't create FormControls, FromGroups or FormArrays
 		// (identified by their 'noFormControl' flag)
 		// (identified by their 'noFormControl' flag)

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

@@ -320,6 +320,8 @@ class RepeatingField<T> {
 	initialRepeat: number = 1;
 	initialRepeat: number = 1;
 	showAddControl:  boolean = true;
 	showAddControl:  boolean = true;
 	showDeleteControl: boolean = true;
 	showDeleteControl: boolean = true;
+	addControlLabel: string = '';
+	deleteControlLabel: string = '';
 	constructor(repeatingFieldMeta: StringMap<any>) {
 	constructor(repeatingFieldMeta: StringMap<any>) {
 		Object.assign(this, repeatingFieldMeta);
 		Object.assign(this, repeatingFieldMeta);
 		if (typeof this.label === 'undefined') {
 		if (typeof this.label === 'undefined') {
@@ -365,8 +367,10 @@ class RepeatingContainer {
 	initialRepeat: number = 1;
 	initialRepeat: number = 1;
 	showAddControl:  boolean = true;
 	showAddControl:  boolean = true;
 	showDeleteControl: boolean = true;
 	showDeleteControl: boolean = true;
+	addControlLabel: string = '';
+	deleteControlLabel: string = '';
 	primaryField: string = '';
 	primaryField: string = '';
-	display: string = 'SINGLE'; // Display strategy to use  - ALL or SINGLE - All at once, or one at a time (with a switcher)
+	display: string = 'ALL'; // Display strategy to use  - ALL or SINGLE - All at once, or one at a time (with a switcher)
 	constructor(containerMeta: StringMap<any>) {
 	constructor(containerMeta: StringMap<any>) {
 		Object.assign(this, containerMeta);
 		Object.assign(this, containerMeta);
 		if (typeof this.label === 'undefined') {
 		if (typeof this.label === 'undefined') {

+ 36 - 36
src/app/dynaform/services/_formdata-utils.ts

@@ -26,7 +26,6 @@
 import { FormBuilder, FormGroup, FormArray, FormControl, AbstractControl, AbstractControlOptions } from '@angular/forms';
 import { FormBuilder, FormGroup, FormArray, FormControl, AbstractControl, AbstractControlOptions } from '@angular/forms';
 import { cloneDeep, omit, reduce } from 'lodash/fp';
 import { cloneDeep, omit, reduce } from 'lodash/fp';
 import * as fmdModels from '../models/field.model';
 import * as fmdModels from '../models/field.model';
-import { meta } from '@mock/testfields.v1';
 
 
 
 
 // ---------------------------------------------------------------------------------------------------------------------
 // ---------------------------------------------------------------------------------------------------------------------
@@ -77,25 +76,25 @@ const combineExtraMeta = (metaG, extraMeta, createFromExtra = false, containerSe
 			const metaFoG = metaG[key] || {};
 			const metaFoG = metaG[key] || {};
 			const seed = isCon ? {} : containerSeed; // Container's don't seed themselves, only their children
 			const seed = isCon ? {} : containerSeed; // Container's don't seed themselves, only their children
 
 
-			/*
-			console.log('******************* BEFORE MODELLING *******************');
-			console.log(val);
-			console.log('IS REPEATING', isRepeating(val));
-			console.log('HAS META', hasMeta(val));
-			console.log('IS ARRAY', Array.isArray(val.meta));
-			console.log('MEMBER 1 DEFINED', !!val.meta[0]);
-			console.log('OBJECT VALUES .........', Object.values(val.meta[0]));
-			console.log('MEMBER 1 1st entry .........', Object.values(val.meta[0])[0]);
-			console.log('MEMBER 1 1st entry is SCALAR', isScalar(Object.values(val.meta[0])));
-			*/
-			/*
-			console.log('IS REP Field', isRepeatingField(val));
-			console.log('IS REP Container', isRepeatingContainer(val));
-			console.log('IS ORD Container', isContainer(val));
-			*/
+			// console.log('******************* BEFORE MODELLING *******************');
+			// console.log(key);
+			// console.log(val);
+			// console.log('IS REPEATING', isRepeating(val));
+			// console.log('HAS META', hasMeta(val));
+			// console.log('IS ARRAY', Array.isArray(val.meta));
+			// console.log('MEMBER 1 DEFINED', !!val.meta[0]);
+			// console.log('OBJECT VALUES .........', Object.values(val.meta[0] || null));
+			// console.log('MEMBER 1 1st entry .........', Object.values(val.meta[0])[0]);
+			// console.log('MEMBER 1 1st entry is SCALAR', isScalar(Object.values(val.meta[0])));
+			// console.log('IS REP Field', isRepeatingField(val));
+			// console.log('IS REP Container', isRepeatingContainer(val));
+			// console.log('IS ORD Container', isContainer(val));
+
 			if (isRepeatingContainer(val))
 			if (isRepeatingContainer(val))
 			{
 			{
 				// We've got a Repeating Container
 				// We've got a Repeating Container
+				metaFoG.type = 'RepeatingContainer';
+				const extraMetaTemplate = Array.isArray(val.meta) ? val.meta[0] || {} : val.meta;
 				const baseObjWithAllKeys = getRCBaseObjectWithAllKeys(metaFoG, val, createFromExtra);
 				const baseObjWithAllKeys = getRCBaseObjectWithAllKeys(metaFoG, val, createFromExtra);
 				metaFoG.meta = generateRepeatedGroup(metaFoG, val, baseObjWithAllKeys);
 				metaFoG.meta = generateRepeatedGroup(metaFoG, val, baseObjWithAllKeys);
 				const extra = {
 				const extra = {
@@ -103,7 +102,7 @@ const combineExtraMeta = (metaG, extraMeta, createFromExtra = false, containerSe
 					meta: metaFoG.meta.map(
 					meta: metaFoG.meta.map(
 						rgMem => combineExtraMeta(
 						rgMem => combineExtraMeta(
 							rgMem.meta,
 							rgMem.meta,
-							val['meta'][0],
+							extraMetaTemplate,
 							createFromExtra,
 							createFromExtra,
 							val['seed'] || containerSeed
 							val['seed'] || containerSeed
 						)
 						)
@@ -111,11 +110,11 @@ const combineExtraMeta = (metaG, extraMeta, createFromExtra = false, containerSe
 				};
 				};
 				combinedMeta[key] = combineMetaForField(metaFoG, {}, extra);
 				combinedMeta[key] = combineMetaForField(metaFoG, {}, extra);
 
 
-				// Stash a 'conbtainer template' for adding extra containers to the repeating container
-				combinedMeta[key].__containerTemplate = combineExtraMeta(
+				// Stash a 'container template' for adding extra containers to the repeating container
+				combinedMeta[key].__template = combineExtraMeta(
 					cloneDeep(baseObjWithAllKeys),
 					cloneDeep(baseObjWithAllKeys),
-					val['meta'][0],
-					false,
+					extraMetaTemplate,
+					createFromExtra,
 					val['seed'] || containerSeed
 					val['seed'] || containerSeed
 				);
 				);
 			}
 			}
@@ -124,11 +123,12 @@ const combineExtraMeta = (metaG, extraMeta, createFromExtra = false, containerSe
 				let extra: StringMap<any>;
 				let extra: StringMap<any>;
 				if (isCon) {
 				if (isCon) {
 					// We've got a container
 					// We've got a container
+					metaFoG.type = 'Container';
 					extra = {
 					extra = {
 						...val,
 						...val,
 						meta: combineExtraMeta( // RECURSION
 						meta: combineExtraMeta( // RECURSION
 							metaFoG.meta || {},
 							metaFoG.meta || {},
-							val['meta'],
+							val.meta,
 							createFromExtra,
 							createFromExtra,
 							val['seed'] || containerSeed // Inherit seeded data if this group's seed isn't set
 							val['seed'] || containerSeed // Inherit seeded data if this group's seed isn't set
 						)
 						)
@@ -138,6 +138,7 @@ const combineExtraMeta = (metaG, extraMeta, createFromExtra = false, containerSe
 				{
 				{
 					if (isRepeating(val)) {
 					if (isRepeating(val)) {
 						// We've got a repeating field
 						// We've got a repeating field
+						metaFoG.type = 'RepeatingField';
 						const metaForFieldToRepeat = {
 						const metaForFieldToRepeat = {
 							...containerSeed,
 							...containerSeed,
 							...omit(['seed', 'minRepeat', 'maxRepeat', 'initialRepeat', 'showAddControl', 'showDeleteControl'], val as StringMap<any>),
 							...omit(['seed', 'minRepeat', 'maxRepeat', 'initialRepeat', 'showAddControl', 'showDeleteControl'], val as StringMap<any>),
@@ -147,7 +148,7 @@ const combineExtraMeta = (metaG, extraMeta, createFromExtra = false, containerSe
 						extra = {
 						extra = {
 							...val,
 							...val,
 							meta: Array.from(Array(val.initialRepeat || 1).keys()).map((f, i) => metaForFieldToRepeat),
 							meta: Array.from(Array(val.initialRepeat || 1).keys()).map((f, i) => metaForFieldToRepeat),
-							__fieldTemplate: metaForFieldToRepeat
+							__template: metaForFieldToRepeat
 						}
 						}
 					} else {
 					} else {
 						// We've got a standard field
 						// We've got a standard field
@@ -171,14 +172,15 @@ const generateRepeatedGroup = (metaFoG, extraMeta, baseObjWithAllKeys): StringMa
 	const repeatInAutoMeta = Array.isArray(metaFoG.meta) ? metaFoG.meta.length : 0;
 	const repeatInAutoMeta = Array.isArray(metaFoG.meta) ? metaFoG.meta.length : 0;
 	const repeatInExtraMeta = extraMeta['initialRepeat'] || extraMeta['minRepeat'];
 	const repeatInExtraMeta = extraMeta['initialRepeat'] || extraMeta['minRepeat'];
 	const repeat = Math.max(repeatInAutoMeta, repeatInExtraMeta);
 	const repeat = Math.max(repeatInAutoMeta, repeatInExtraMeta);
-
-	metaFoG.meta = metaFoG.meta.map( rcMem => ({ ...rcMem, meta: { ...baseObjWithAllKeys, ...rcMem.meta } }) ); // Add extra keys to model meta
+	// console.log('generateRepeatedGroup');
+	// console.log(metaFoG, extraMeta, baseObjWithAllKeys);
+	metaFoG.meta = (metaFoG.meta || []).map( rcMem => ({ ...rcMem, meta: { ...baseObjWithAllKeys, ...rcMem.meta } }) ); // Add extra keys to model meta
 
 
 	// Extend repeated group from model (if any) to correct length, and add any missing names
 	// Extend repeated group from model (if any) to correct length, and add any missing names
 	const repeatedGroup = repeatInAutoMeta ?
 	const repeatedGroup = repeatInAutoMeta ?
 		[ ...metaFoG.meta, ...Array(repeat - repeatInAutoMeta).fill({ meta: baseObjWithAllKeys }) ] :
 		[ ...metaFoG.meta, ...Array(repeat - repeatInAutoMeta).fill({ meta: baseObjWithAllKeys }) ] :
 		Array(repeat).fill({ meta: baseObjWithAllKeys });
 		Array(repeat).fill({ meta: baseObjWithAllKeys });
-	const fullyNamedRepeatedGroup = repeatedGroup.map((rgMem, i) => rgMem.name ? rgMem : { name: `group${i + 1}`, ...rgMem });
+	const fullyNamedRepeatedGroup = repeatedGroup.map((rgMem, i) => rgMem.name ? { ...rgMem } : { name: `group${i + 1}`, ...rgMem });
 	return fullyNamedRepeatedGroup;
 	return fullyNamedRepeatedGroup;
 }
 }
 
 
@@ -192,7 +194,6 @@ const getRCBaseObjectWithAllKeys = (metaFoG, extraMeta, createFromExtra = false)
 	return baseObjWithAllKeys;
 	return baseObjWithAllKeys;
 }
 }
 
 
-
 // ---------------------------------------------------------------------------------------------------------------------
 // ---------------------------------------------------------------------------------------------------------------------
 // Build Form-Field-Type-Specific Metadata (using the field models in dynaform/models)
 // Build Form-Field-Type-Specific Metadata (using the field models in dynaform/models)
 // ---------------------------------------------------------------------------------------------------------------------
 // ---------------------------------------------------------------------------------------------------------------------
@@ -249,16 +250,15 @@ const buildFieldSpecificMetaInClosure = (metaG, context) => {
 		*/
 		*/
 		if (isRepeatingField(modeledGroupMember)) {
 		if (isRepeatingField(modeledGroupMember)) {
 			modeledGroupMember.meta = modeledGroupMember.meta.map(metaF => buildModeledField(metaF));
 			modeledGroupMember.meta = modeledGroupMember.meta.map(metaF => buildModeledField(metaF));
-			modeledGroupMember.__fieldTemplate = buildModeledField(modeledGroupMember.__fieldTemplate);
+			modeledGroupMember.__template = buildModeledField(modeledGroupMember.__template);
 		} else if (isContainer(modeledGroupMember)) {
 		} else if (isContainer(modeledGroupMember)) {
 			modeledGroupMember.meta = _buildFieldSpecificMeta(modeledGroupMember.meta);
 			modeledGroupMember.meta = _buildFieldSpecificMeta(modeledGroupMember.meta);
 		} else if (isRepeatingContainer(modeledGroupMember)) {
 		} else if (isRepeatingContainer(modeledGroupMember)) {
 			modeledGroupMember.meta = modeledGroupMember.meta.map(rcMem => ({ ...rcMem, meta: _buildFieldSpecificMeta(rcMem.meta) }));
 			modeledGroupMember.meta = modeledGroupMember.meta.map(rcMem => ({ ...rcMem, meta: _buildFieldSpecificMeta(rcMem.meta) }));
-			modeledGroupMember.__containerTemplate = {
-				...modeledGroupMember.meta[0],
-				meta: _buildFieldSpecificMeta(modeledGroupMember.__containerTemplate),
-				name: '__containerTemplate',
-				button: ''
+			modeledGroupMember.__template = {
+				...modeledGroupMember.meta[0] || {},
+				meta: _buildFieldSpecificMeta(modeledGroupMember.__template),
+				name: '__template'
 			};
 			};
 		}
 		}
 		return modeledGroupMember;
 		return modeledGroupMember;
@@ -317,7 +317,7 @@ const prependParentPathRecursive = (parentPath: string, obj: StringMap<any>) =>
 
 
 const _extractFieldMapping = ( [key, metaFoG] ) => {
 const _extractFieldMapping = ( [key, metaFoG] ) => {
 	let source;
 	let source;
-	if (hasMeta(metaFoG)) {
+	if (hasMeta(metaFoG) && !Array.isArray(metaFoG.meta)) { // If it has non-array meta (note, we patch entire arrays for array meta)
 		if (Array.isArray(metaFoG.source)) {
 		if (Array.isArray(metaFoG.source)) {
 			source = extractFieldMappings(metaFoG.meta);
 			source = extractFieldMappings(metaFoG.meta);
 			source.__ = metaFoG.source; // Store the functional mapping (including function executed later to provide container's data)
 			source.__ = metaFoG.source; // Store the functional mapping (including function executed later to provide container's data)
@@ -763,7 +763,7 @@ const addNameToSelfAndChildren = ( [key, metaFoG] ) => {
 	metaFoG = addNameIfMissing(metaFoG, key);
 	metaFoG = addNameIfMissing(metaFoG, key);
 	if (hasMeta(metaFoG) && !isRepeatingContainer(metaFoG)) {
 	if (hasMeta(metaFoG) && !isRepeatingContainer(metaFoG)) {
 		if (metaFoG.meta.length && metaFoG.meta.every(isScalar)) {
 		if (metaFoG.meta.length && metaFoG.meta.every(isScalar)) {
-			metaFoG.meta = metaFoG.meta.map(val => ({ name: val, value: val }));
+			metaFoG.meta = metaFoG.meta.map(val => ({ name: val, value: val, label: val.replace(/_/g, ' ') }));
 		} else {
 		} else {
 			metaFoG.meta = isArray(metaFoG.meta) ? Object.values(addMissingNames(metaFoG.meta)) : addMissingNames(metaFoG.meta); // Recursion
 			metaFoG.meta = isArray(metaFoG.meta) ? Object.values(addMissingNames(metaFoG.meta)) : addMissingNames(metaFoG.meta); // Recursion
 		}
 		}

+ 108 - 1
src/app/dynaform/services/dynaform.service.ts

@@ -98,7 +98,7 @@
  */
  */
 
 
 import { Injectable, ComponentRef } from '@angular/core';
 import { Injectable, ComponentRef } from '@angular/core';
-import { FormBuilder, FormGroup } from '@angular/forms';
+import { FormBuilder, FormGroup, FormArray } from '@angular/forms';
 import { SuperForm } from 'angular-super-validator';
 import { SuperForm } from 'angular-super-validator';
 import { ModelMapperService } from './model-mapper.service';
 import { ModelMapperService } from './model-mapper.service';
 
 
@@ -111,8 +111,12 @@ import {
 	generateNewModel, updateMeta
 	generateNewModel, updateMeta
 } from './_formdata-utils';
 } from './_formdata-utils';
 
 
+import { buildFormControl } from './_formdata-utils'; // MAY NOT BE NEEDED AFTER REFACTORING FormArrayLengthening logic to formdata-utils
+
 import { Option } from './../models/field.model';
 import { Option } from './../models/field.model';
 
 
+import { get  as _get, cloneDeep } from 'lodash/fp';
+
 
 
 export interface IFormAndMeta {
 export interface IFormAndMeta {
 	form: FormGroup;
 	form: FormGroup;
@@ -189,6 +193,109 @@ export class DynaformService {
 	updateForm(newModel: StringMap<any>, form?: FormGroup, meta?: StringMap<any>): void {
 	updateForm(newModel: StringMap<any>, form?: FormGroup, meta?: StringMap<any>): void {
 		const mapping = extractFieldMappings(meta || this.meta); // Memoize
 		const mapping = extractFieldMappings(meta || this.meta); // Memoize
 		const mappedModel = this.modelMapper.reverseMap(newModel, mapping);
 		const mappedModel = this.modelMapper.reverseMap(newModel, mapping);
+
+		meta = meta || this.meta;
+		form = form || this.form;
+
+		// When updating with a model that contains RepeatedFields or RepeatedContainers
+		// adjust the lengths of the displayed fields or containers appropiately
+		function findArraysRecursive(model, path = []) {
+			Object.entries(model).forEach(([key, val]) => {
+				if (Array.isArray(val)) {
+					// console.log('---------------------------');
+					// console.log([...path, key].join('.'));
+					const concreteMeta = getConcreteMetaForModelPath([ ...path, key ]);
+					const templateMeta = getTemplateMetaForModelPath([ ...path, key ]);
+					if (!templateMeta) {
+						return;
+					}
+					if (templateMeta.type === 'RepeatingField' || templateMeta.type === 'RepeatingContainer') {
+						// console.log(concreteMeta);
+						// console.log(templateMeta);
+						const delta = val.length - concreteMeta.meta.length;
+						// console.log('delta', delta);
+						const formArray = form.get([ ...path, key]) as FormArray;
+						if (delta > 0) {
+							// Array length in model exceeds length of corresponding metadata array
+							// console.log(templateMeta);
+							const desiredLength = Math.min(val.length, templateMeta.maxRepeat);
+							let lengthenBy = desiredLength - concreteMeta.meta.length;
+							// console.log('desiredLength', desiredLength);
+							// console.log('lengthenBy', lengthenBy);
+							const templateKey = concreteMeta.type === 'RepeatingField' ? '__template' : '__template';
+							const template = concreteMeta[templateKey];
+							concreteMeta.meta = [ ...concreteMeta.meta, ...Array(lengthenBy).fill(null).map(() => cloneDeep(template)) ];
+							while (lengthenBy--) {
+								switch(concreteMeta.type) {
+									case 'RepeatingField':
+										const newFormControl = buildFormControl(concreteMeta.__template);
+										formArray.push(newFormControl);
+										break;
+									case 'RepeatingContainer':
+										const buildFormGroup = buildFormGroupFunctionFactory(new FormBuilder());
+										const newFormGroup = buildFormGroup(concreteMeta.__template.meta);
+										formArray.push(newFormGroup);
+										break;
+								}
+							}
+						} else if (delta < 0) {
+							// Array length in model is shorter that length of corresponding metadata array
+							const desiredLength = Math.max(val.length, templateMeta.minRepeat);
+							let shortenBy = concreteMeta.meta.length - desiredLength;
+							// console.log('desiredLength', desiredLength);
+							// console.log('shortenBy', shortenBy, 'length', concreteMeta.meta.length);
+							concreteMeta.meta.length = desiredLength; // Truncate the array by setting the length
+							while (shortenBy--) {
+								formArray.removeAt(formArray.length - 1);
+							}
+						}
+					}
+					if (templateMeta.type === 'RepeatingContainer') {
+						// Does the value contain nested array values for RepeatingFields or RepeatingContainers?
+						val.map((v, i) => findArraysRecursive(v, [ ...path, key, i]));
+					}
+				} else if (val && typeof val === 'object') {
+					findArraysRecursive(val, [ ...path, key ]);
+				}
+			});
+		}
+		findArraysRecursive(mappedModel);
+
+		function getConcreteMetaForModelPath(path: string | string[]): StringMap<any> {
+			const _path = typeof path === 'string' ? path.split('.') : path;
+			// const deepMetaPath = _path.join('.meta.').replace(/\.\d+$/, '');
+			const deepMetaPath = _path.join('.meta.');
+			// console.log(deepMetaPath);
+			return _get(deepMetaPath, meta);
+		}
+
+		function getTemplateMetaForModelPath(path: string | string[]): StringMap<any> {
+			const _path = typeof path === 'string' ? path.split('.') : path;
+			const deepMetaTemplatePath = _path.join('.meta.').replace(/\.meta\.\d+/g, '.__template');
+			// console.log(deepMetaTemplatePath);
+			return _get(deepMetaTemplatePath, meta);
+		}
+
+		// console.log('*************************************');
+		// console.log(meta.report);
+		// console.log(getConcreteMetaForModelPath('report.clauses.0'));
+		// console.log(getTemplateMetaForModelPath('report.clauses.0'));
+		// console.log(getConcreteMetaForModelPath('report.clauses.0.subClauses'));
+		// console.log(getTemplateMetaForModelPath('report.clauses.0.subClauses'));
+		// console.log('*************************************');
+
+		// console.log(meta.report.meta.clauses.meta[0]);
+		// console.log(meta.report.meta.clauses.meta[0] === meta.report.meta.clauses.meta[0]);
+		// console.log(meta.report.meta.clauses.meta[0] === meta.report.meta.clauses.meta[1]);
+		// console.log(meta.report.meta.clauses.meta[0] === cloneDeep(meta.report.meta.clauses.meta[0]));
+
+		// console.log('HERE');
+		// console.log(meta.report.meta.clauses.meta[0].meta.subClauses.meta.length);
+		// console.log(meta.report.meta.clauses.meta[1].meta.subClauses.meta.length);
+		// console.log(meta.report.meta.clauses.meta[0].meta.subClauses.meta[0] === meta.report.meta.clauses.meta[0].meta.subClauses.meta[0]);
+		// console.log(meta.report.meta.clauses.meta[0].meta.subClauses.meta[0] === meta.report.meta.clauses.meta[0].meta.subClauses.meta[1]);
+		// console.log(meta.report.meta.clauses.meta[0].meta.subClauses.meta[0] === cloneDeep(meta.report.meta.clauses.meta[0].meta.subClauses.meta[0]));
+
 		(form || this.form).patchValue(mappedModel);
 		(form || this.form).patchValue(mappedModel);
 		(form || this.form).updateValueAndValidity();
 		(form || this.form).updateValueAndValidity();
 	}
 	}

+ 49 - 41
src/app/dynaform/services/model-mapper.service.ts

@@ -50,57 +50,65 @@ const mapping = {
 
 
 (1) forwardMap gives:
 (1) forwardMap gives:
 
 
-{ b: { c: 555, e: 99 },
-  e: { f: { g: 777 } },
-  r: 1000,
-  s: 2000,
-  t: { u: 3000 },
-  d: 44,
-  f: { value: 2, isEven: true },
-  g: { value: 3, isEven: false },
-  y: { hhh: { value: 100000, isEven: true } },
-  i: 'MAPPING ERROR: Unknown mapping type in mapping at i' }
+{
+	b: { c: 555, e: 99 },
+	e: { f: { g: 777 } },
+	r: 1000,
+	s: 2000,
+	t: { u: 3000 },
+	d: 44,
+	f: { value: 2, isEven: true },
+	g: { value: 3, isEven: false },
+	y: { hhh: { value: 100000, isEven: true } },
+	i: 'MAPPING ERROR: Unknown mapping type in mapping at i' }
+}
 
 
 (2) lazyForwardMap (which also copies properties not explicitly specified in the mapping) gives:
 (2) lazyForwardMap (which also copies properties not explicitly specified in the mapping) gives:
 
 
-{ b: { c: 555, e: 99 },
-  e: { f: { g: 777 } },
-  r: 1000,
-  s: 2000,
-  t: { u: 3000 },
-  c: { lazy: 'everybody\'s lazy' },
-  d: 44,
-  f: { value: 2, isEven: true },
-  g: { value: 3, isEven: false },
-  y: { hhh: { value: 100000, isEven: true }, name: 'lost in action' },
-  i: 'MAPPING ERROR: Unknown mapping type in mapping at i',
-  z: 'hello' }
+{
+	b: { c: 555, e: 99 },
+	e: { f: { g: 777 } },
+	r: 1000,
+	s: 2000,
+	t: { u: 3000 },
+	c: { lazy: 'everybody\'s lazy' },
+	d: 44,
+	f: { value: 2, isEven: true },
+	g: { value: 3, isEven: false },
+	y: { hhh: { value: 100000, isEven: true }, name: 'lost in action' },
+	i: 'MAPPING ERROR: Unknown mapping type in mapping at i',
+	z: 'hello'
+}
 
 
 (3) reverseMap (on either of the forwardMap results) regenerates the explicitly mapped parts of the original model:
 (3) reverseMap (on either of the forwardMap results) regenerates the explicitly mapped parts of the original model:
 
 
-{ a: 555,
-  b: 777,
-  c: { r: 1000, s: 2000, t: 3000 },
-  d: 22,
-  e: 33,
-  f: 2,
-  g: 3,
-  h: 100000 }
+{
+	a: 555,
+	b: 777,
+	c: { r: 1000, s: 2000, t: 3000 },
+	d: 22,
+	e: 33,
+	f: 2,
+	g: 3,
+	h: 100000
+}
 
 
 (4) lazyReverseMap also copies additional properties, if it can
 (4) lazyReverseMap also copies additional properties, if it can
 	(it will merge objects without overwriting exissting properties, and won't overwrite scalar properties)
 	(it will merge objects without overwriting exissting properties, and won't overwrite scalar properties)
 
 
-{ a: 555,
-  b: 777,
-  c: { r: 1000, s: 2000, t: 3000, lazy: 'everybody\'s lazy' },
-  d: 22,
-  e: 33,
-  f: 2,
-  g: 3,
-  h: 100000,
-  y: { hhh: { value: 100000, isEven: true }, name: 'lost in action' },
-  i: 'MAPPING ERROR: Unknown mapping type in mapping at i',
-  z: 'hello' }
+{
+	a: 555,
+	b: 777,
+	c: { r: 1000, s: 2000, t: 3000, lazy: 'everybody\'s lazy' },
+	d: 22,
+	e: 33,
+	f: 2,
+	g: 3,
+	h: 100000,
+	y: { hhh: { value: 100000, isEven: true }, name: 'lost in action' },
+	i: 'MAPPING ERROR: Unknown mapping type in mapping at i',
+	z: 'hello'
+}
 
 
 */
 */
 
 

+ 39 - 32
src/styles.scss

@@ -10,6 +10,8 @@ $col-blue-primary: #0085bc;
 
 
 $col-charcoal: #444;
 $col-charcoal: #444;
 
 
+$col-danger: red;
+
 // --------------------------------------------------------------------------------------------------------------------
 // --------------------------------------------------------------------------------------------------------------------
 
 
 // Clarity Dependency SCSS - No loner needed as of Clarity 2?
 // Clarity Dependency SCSS - No loner needed as of Clarity 2?
@@ -74,7 +76,7 @@ div.clr-col-sm-8 {
 	}
 	}
 }
 }
 
 
-.short-field {
+.multicolor {
 	input, select {
 	input, select {
 		border-width: 1px;
 		border-width: 1px;
 		border-style: solid;
 		border-style: solid;
@@ -83,7 +85,7 @@ div.clr-col-sm-8 {
 		border-right-color: teal;
 		border-right-color: teal;
 		border-bottom-width: 2px;
 		border-bottom-width: 2px;
 		border-bottom-color: magenta;
 		border-bottom-color: magenta;
-		width: 60px;
+		width: 200px;
 		padding-left: 10px;
 		padding-left: 10px;
 	}
 	}
 }
 }
@@ -186,27 +188,6 @@ input, textarea, select {
 	.btn { margin-left: 0; margin-right: 4px; }
 	.btn { margin-left: 0; margin-right: 4px; }
 }
 }
 
 
-// ---------------------------------------------------------------------------------------------------------------------
-// Repeating Fields
-
-// dyna-rf = Dynaform Repeating Field
-
-.dyna-rf-container {
-	// outline: 1px lime solid;
-	label {
-		display: none;
-	}
-	> div:first-of-type {
-		label {
-			display: block;
-		}
-	}
-}
-
-// .dyna-rf-field {
-// 	outline: 1px pink solid;
-// }
-
 // ---------------------------------------------------------------------------------------------------------------------
 // ---------------------------------------------------------------------------------------------------------------------
 // Repeating Containers (series of buttons alowwing user to focus a repeating container member)
 // Repeating Containers (series of buttons alowwing user to focus a repeating container member)
 
 
@@ -222,35 +203,61 @@ input, textarea, select {
 	}
 	}
 }
 }
 
 
-.dyna-rc-container.dyna-rc-display-all, .dyna-rc-control.dyna-rc-display-all {
-	border-bottom: 4px #CCC solid;
+.dyna-rc-member-container.dyna-rc-display-all, .dyna-rc-control.dyna-rc-display-all {
+	// border-bottom: 4px #CCC solid;
 	padding-bottom: 1rem;
 	padding-bottom: 1rem;
-	&:first-child {
-		border-top: 4px #CCC solid;
-		> button {
-			margin-top: 1rem;
-		}
-	}
 }
 }
+
 // ---------------------------------------------------------------------------------------------------------------------
 // ---------------------------------------------------------------------------------------------------------------------
 // Repeating Field and Container Buttons
 // Repeating Field and Container Buttons
 
 
+.dyna-rep-add-container {
+	@include clearfix;
+}
+
 .dyna-rep-btn-add {
 .dyna-rep-btn-add {
 	margin-left: 0;
 	margin-left: 0;
 }
 }
 
 
 .dyna-rep-btn-delete {
 .dyna-rep-btn-delete {
 	float: right;
 	float: right;
-	margin-top: 0;
 	margin-right: 0;
 	margin-right: 0;
+	padding: 0;
+	color: $col-charcoal;
+	cursor: pointer;
+	.dyna-rf-field & {
+		background-color: transparent;
+		border: 0;
+		transform: translateY(16px);
+		&:disabled {
+			color: #AAA !important;
+			cursor: not-allowed;
+		}
+		&:hover {
+			color: $col-danger;
+		}
+	}
+	.dyna-rc-member-container & {
+		&:hover {
+			color: $col-danger;
+		}
+		&:disabled {
+			cursor: not-allowed;
+		}
+	}
 }
 }
 
 
+
 // ---------------------------------------------------------------------------------------------------------------------
 // ---------------------------------------------------------------------------------------------------------------------
 
 
 .dyna-hidden {
 .dyna-hidden {
 	display: none;
 	display: none;
 }
 }
 
 
+div:not(.row-checkbox) + div.row-checkbox {
+	margin-top: 8px;
+}
+
 // ---------------------------------------------------------------------------------------------------------------------
 // ---------------------------------------------------------------------------------------------------------------------
 // Errors
 // Errors