Forráskód Böngészése

Implemented seeded metatdata for groups, which gets inherited by both fields and nested groups

Richard Knight 6 éve
szülő
commit
c5768aba99

+ 25 - 0
src/app/_mock/testfields.v11.ts

@@ -0,0 +1,25 @@
+// TESTS: Contaiuners seeding metadata (except source, which is currently handled separately)
+
+const model = {};
+
+const meta = {
+	container: {
+		label: 'Fields should inherit seeded meta from the container',
+		seed: { class: 'short-field', value: 5 },
+		meta: {
+			a: {},
+			b: {},
+			c: { value: 6 },
+			d: {},
+			nestedContiner: {
+				label: 'More deeply nested container',
+				meta: {
+					e: {},
+					f: {}
+				}
+			}
+		}
+	}
+};
+
+export { model, meta };

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

@@ -4,7 +4,7 @@
 			<h1>NgDynaform</h1>
 			<p>
 			Dynamic Form Layout Module<br>
-			Load different tests by appending a query param to the URL <b>?test=N</b> (where N is a number between 1 and 10).<br>
+			Load different tests by appending a query param to the URL <b>?test=N</b> (where N is a number between 1 and 11).<br>
 			NOTE: Model set to update on change, but this can be set to blur or submit for less re-rendering.
 			</p>
 		</div>

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

@@ -12,8 +12,9 @@ import * as test7 from './_mock/testfields.v7';
 import * as test8 from './_mock/testfields.v8';
 import * as test9 from './_mock/testfields.v9';
 import * as test10 from './_mock/testfields.v10';
+import * as test11 from './_mock/testfields.v11';
 
-const testdata = [ test1, test2, test3, test4, test5, test6, test7, test8, test9, test10 ];
+const testdata = [ test1, test2, test3, test4, test5, test6, test7, test8, test9, test10, test11 ];
 
 const defatltTest = 10;
 
@@ -85,7 +86,7 @@ export class AppComponent implements OnInit, OnChanges {
 	ngOnChanges() {
 		console.log(this.form.errors);
 	}
-	
+
 	handleCallback(fnId) {
 		this.dynaform.call(fnId);
 	}

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

@@ -18,7 +18,7 @@ export abstract class GroupInputComponent implements OnInit {
 	ngOnInit() {
 		// Move meta variables up a level, for direct access in templates
 		this.exposeMetaInTemplate.map(p => this[p] = this.meta[p] !== undefined ? this.meta[p] : this[p]);
-		
+
 		// Get the FormGroup, and information about the controls inside it
 		this.formGroup = this.control as FormGroup;
 		this.childMetaArray = Object.values(this.meta.meta); // Metadata array of all controls in group

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

@@ -5,7 +5,7 @@ import {
 } from '@angular/core';
 import {
 	Form, FormControl, AbstractControl,
-	NgControl, ControlContainer, ControlValueAccessor, 
+	NgControl, ControlContainer, ControlValueAccessor,
 	NG_VALIDATORS, Validator, Validators, ValidatorFn,
 	NG_ASYNC_VALIDATORS, AsyncValidatorFn
 } from '@angular/forms';
@@ -16,7 +16,7 @@ interface FFC {
 	control: FormControl; // Remember, this can be an individual FormControl or a FormGroup
 	meta: StringMap;
 	propagateChange?: Function;
-	call?: EventEmitter<string>
+	call?: EventEmitter<string>;
 }
 type FFCCustom = FFC & ControlValueAccessor;
 
@@ -156,8 +156,8 @@ export class DynafieldDirective extends NgControl implements OnInit, OnDestroy {
 		return this.cc ? this.cc.formDirective : null;
 	}
 
-	get validator(): ValidatorFn | null { 
-        return this.validators !== null ? Validators.compose(this.validators.map(this.normalizeValidator)) : null;
+	get validator(): ValidatorFn | null {
+		return this.validators !== null ? Validators.compose(this.validators.map(this.normalizeValidator)) : null;
 	}
 
 	get asyncValidator(): AsyncValidatorFn {
@@ -172,6 +172,6 @@ export class DynafieldDirective extends NgControl implements OnInit, OnDestroy {
 			return <ValidatorFn>validator;
 		}
 	}
-	
+
 }
 

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

@@ -223,7 +223,7 @@ class CheckbuttonGroup {
 // ---------------------------------------------------------------------------------------------------------------------
 // Concrete Implementations - Kendo Form Components
 
-class TimepickerField<TimePickerFieldMetaData> extends SimpleField {
+class TimepickerField extends SimpleField {
 	type = 'Timepicker';
 	value: Date | string;
 	format = 'hh:mm a';
@@ -232,7 +232,7 @@ class TimepickerField<TimePickerFieldMetaData> extends SimpleField {
 		super(meta);
 		if (typeof this.value === 'string') {
 			const [hh, mm, ss] = this.value.split(':');
-			this.value = new Date(2000, 6, 1, +hh | 0, +mm | 0, +ss | 0);
+			this.value = new Date(2000, 6, 1, +hh || 0, +mm || 0, +ss || 0);
 		}
 		if (!(this.value instanceof Date)) {
 			this.value = new Date();
@@ -253,9 +253,11 @@ class Container {
 	type = 'Container';
 	name: string;
 	label = '';
+	seed: StringMap;
 	template?: TemplateRef<any>;
 	meta: StringMap; // TODO: Tighten up on type with union type
 	constructor(containerMeta: StringMap) {
+		console.log(containerMeta);
 		Object.assign(this, containerMeta);
 		if (typeof this.label === 'undefined') {
 			this.label = unCamelCase(this.name);
@@ -276,7 +278,7 @@ interface ButtonInterface {
 class Button implements ButtonInterface {
 	label;
 	fnId;
-	class: string = 'btn-primary';
+	class = 'btn-primary';
 	constructor(buttonProps) {
 		Object.assign(this, buttonProps);
 	}
@@ -299,8 +301,8 @@ class ButtonGroup {
 
 class Heading {
 	type = 'Heading';
-	text: string = 'Missing Heading Text';
-	level: number = 3;
+	text = 'Missing Heading Text';
+	level = 3;
 	readonly noFormControls = true; // Indicates this has no FormControls associated with it
 	readonly noLabel = true; // Indicates this has no label, so don't create normal form row
 	constructor(meta) {

+ 45 - 27
src/app/dynaform/services/_formdata-utils.ts

@@ -62,14 +62,29 @@ const autoMeta = model => Object.entries(model)
 // Combine automatically generated metadata with overrides
 // ---------------------------------------------------------------------------------------------------------------------
 
-const combineMetaForField = (metaF, extraMetaF) => Object.assign(metaF, extraMetaF);
-const combineExtraMeta = (metaG, extraMeta, createFromExtra = false) => {
+// metaGSeed = Metadata from group which seeds all fields contained in the group
+
+const combineMetaForField = (metaF, metaGSeed, extraMetaF) => Object.assign(metaF, metaGSeed, extraMetaF);
+const combineExtraMeta = (metaG, extraMeta, createFromExtra = false, metaGSeed = {}) => {
 	const combinedMeta = {};
 	Object.entries(extraMeta).forEach(([key, val]) => {
 		if (typeof metaG[key] === 'object' || createFromExtra) {
-			combinedMeta[key] = metaG[key] && (<any>val).meta ?
-				combineMetaForField(metaG[key], { ...val, meta: combineExtraMeta(metaG[key].meta, (<any>val).meta, createFromExtra) }) :
-				combineMetaForField(metaG[key] || {}, val);
+			const isCon = isContainer(val);
+			const metaFoG = metaG[key] || {};
+			const seed = isCon ? {} : metaGSeed;
+			const extra = isCon ?
+				{
+					...val,
+					meta: combineExtraMeta(
+						metaFoG.meta || {},
+						val['meta'],
+						createFromExtra,
+						val['seed'] || metaGSeed // Inherit seeded data if this group's seed isn't set
+					)
+				}
+				:
+				val;
+			combinedMeta[key] = combineMetaForField(metaFoG, seed, extra);
 		}
 	});
 	return { ...metaG, ...combinedMeta };
@@ -131,7 +146,7 @@ const prependParentPathRecursive = (parentPath: string, obj: StringMap) => {
 	return Object.entries(obj)
 		.map( ([key, mapping] ) => {
 			let mappingRes;
-			switch(typeof mapping) {
+			switch (typeof mapping) {
 				case 'string':
 					mappingRes = processPath(parentPath, mapping);
 					break;
@@ -146,14 +161,14 @@ const prependParentPathRecursive = (parentPath: string, obj: StringMap) => {
 							mappingRes = [source, ...mapping];
 						}
 					} else {
-						mappingRes = prependParentPathRecursive(parentPath, mapping)
+						mappingRes = prependParentPathRecursive(parentPath, mapping);
 					}
 					break;
 			}
 			return [key, mappingRes];
 		})
 		.reduce((acc, [key, val]) => addProp(acc, key, val), {});
-}
+};
 
 const _extractFieldMapping = ( [key, metaFoG] ) => {
 	let source;
@@ -173,14 +188,14 @@ const _extractFieldMapping = ( [key, metaFoG] ) => {
 const extractFieldMappings = (metaG, parentPath = '') => Object.entries(metaG)
 	.map(_extractFieldMapping)
 	.reduce((res, [key, mapping]) => {
-		
+
 		// Work out the path prefix
 		let prefix;
 		if (parentPath) {
 			if (isRootPath(parentPath) || isAbsPath(metaG[key].source) || Array.isArray(parentPath)) {
-				 // If the parentPath is the root of the data structure, or the source is an absolute path or functional datasource,
-				 // then there's no path prefix
-				prefix = ''
+				// If the parentPath is the root of the data structure, or the source is an absolute path or functional datasource,
+				// then there's no path prefix
+				prefix = '';
 			} else {
 				// Otherwise create a prefix from the parentPath
 				prefix = parentPath ? (parentPath[0] === '/' ? parentPath.slice(1) : parentPath) : '';
@@ -231,11 +246,11 @@ const extractFieldMappings = (metaG, parentPath = '') => Object.entries(metaG)
 			mappingRes = prependParentPathRecursive(prefix, mapping);
 
 		} else {
-			
+
 			mappingRes = mapping;
 
 		}
-		
+
 		return addProp(res, key, mappingRes);
 	}, {});
 
@@ -254,7 +269,8 @@ const buildFormGroupFunctionFactory = (fb: FormBuilder): (meta) => FormGroup =>
 	const buildValidators = (metaF): AbstractControlOptions => ({
 		validators: metaF.validators,
 		asyncValidators: metaF.asyncValidators,
-		updateOn: metaF.type === 'text' || metaF.type === 'textarea' ? 'blur' : 'change' // blur not working for custom components, so use change for custom and blur for text
+		// blur not working for custom components, so use change for custom and blur for text
+		updateOn: metaF.type === 'text' || metaF.type === 'textarea' ? 'blur' : 'change'
 	});
 	const buildFormControl = metaF => new FormControl(buildControlState(metaF), buildValidators(metaF));
 
@@ -266,7 +282,9 @@ const buildFormGroupFunctionFactory = (fb: FormBuilder): (meta) => FormGroup =>
 		(isArray(metaFoG.meta) ? buildFormArray(metaFoG.meta) : _buildFormGroup(metaFoG.meta)) :
 		buildFormControl(metaFoG);
 
-	const buildFormGroupReducerIteree = (res, metaFoG) => metaFoG.noFormControls ? res : Object.assign(res, { [metaFoG.name]: buildFormGroupMember(metaFoG) });
+	const buildFormGroupReducerIteree = (res, metaFoG) => {
+		return metaFoG.noFormControls ? res : Object.assign(res, { [metaFoG.name]: buildFormGroupMember(metaFoG) });
+	};
 	const _buildFormGroup = _metaG => fb.group(reduce(buildFormGroupReducerIteree, {}, _metaG));
 
 	// The main function - builds FormGroups containing other FormGroups, FormArrays and FormControls
@@ -300,17 +318,17 @@ const objConcat = (obj, pos, key, val = null) => {
 	delete start[key];
 	delete finish[key];
 	return { ...start, [key]: val, ...finish };
-}
+};
 
 const insertBefore = (obj, beforeKey, key, val = null) => {
 	const targetPosition = Object.keys(obj).indexOf(beforeKey);
 	return objConcat(obj, targetPosition, key, val);
-}
+};
 
 const insertAfter = (obj, afterKey, key, val = null) => {
 	const targetPosition = Object.keys(obj).indexOf(afterKey) + 1;
 	return objConcat(obj, targetPosition, key, val);
-}
+};
 
 // Process reordeing instructions recursively
 const _execMetaReorderingInstructions = (metaG: StringMap) => {
@@ -326,11 +344,11 @@ const _execMetaReorderingInstructions = (metaG: StringMap) => {
 		}
 	});
 	return reorderedGroup;
-}
+};
 
 const execMetaReorderingInstructions = (metaG: StringMap) => {
 	return _execMetaReorderingInstructions(cloneDeep(metaG));
-}
+};
 
 
 // ---------------------------------------------------------------------------------------------------------------------
@@ -340,7 +358,7 @@ const execMetaReorderingInstructions = (metaG: StringMap) => {
 
 const generateNewModel = (originalModel, updates) => {
 	return updateObject(originalModel, updates);
-}
+};
 
 const updateObject = (obj, updates, createAdditionalKeys = false) => {
 	// THIS DOES NOT MUTATE obj, instead returning a new object
@@ -354,7 +372,7 @@ const safeSet = (obj, key, val, createAdditionalKeys = false) => {
 	if (!createAdditionalKeys && !obj.hasOwnProperty(key)) {
 		return;
 	}
-	let currentVal = obj[key];
+	const currentVal = obj[key];
 	if (val === currentVal) {
 		return;
 	}
@@ -380,13 +398,13 @@ const safeSet = (obj, key, val, createAdditionalKeys = false) => {
 							Rejected update was ${val}`);
 		}
 	}
-}
+};
 
 const nullOrScaler = val => {
-	if (val === null) return true;
+	if (val === null) { return true; }
 	const t = typeof val;
 	return t === 'number' || t === 'string' || t === 'boolean';
-}
+};
 
 
 // ---------------------------------------------------------------------------------------------------------------------
@@ -436,7 +454,7 @@ const addMissingFieldSpecificMeta = metaG => Object.entries(metaG)
 // Exports
 // ---------------------------------------------------------------------------------------------------------------------
 
-export { 
+export {
 	autoMeta, combineModelWithMeta, combineExtraMeta, execMetaReorderingInstructions,
 	buildFieldSpecificMeta, extractFieldMappings, buildFormGroupFunctionFactory,
 	generateNewModel