Kaynağa Gözat

Wrapping utilities into injectable Dynaform Service

Richard Knight 6 yıl önce
ebeveyn
işleme
f3804b89a0

+ 2 - 3
src/app/_mock/testfields.v2.ts

@@ -1,7 +1,6 @@
 // TESTS: Generation of Modeled MetaData using buildModeledMeta library function
 
 import { ValueTransformer } from './../dynaform/interfaces';
-import { buildModeledMeta } from './../dynaform/libs';
 
 // ---------------------------------------------------------------------------------------------------------------------
 // Native
@@ -172,6 +171,6 @@ const metadata = {
 	container
 };
 
-export const formMetaDataObj = buildModeledMeta(metadata);
+export const formMetaDataObj = metadata;
 
-console.log(formMetaDataObj);
+console.log(formMetaDataObj);

+ 0 - 6
src/app/_mock/testfields.v3.ts

@@ -1,8 +1,6 @@
 // TESTS: Generation of Automatic MetaData from model using autoMeta library function
 // All fields will default to type 'text'
 
-import { buildModeledMeta, autoMeta } from './../dynaform/libs';
-
 const model1 = {
 	a: 'Value 1',
 	b: 'Value 2',
@@ -24,7 +22,3 @@ const model2 = {
 	},
 	z: 'THE END'
 };
-
-export const formMetaDataObj = buildModeledMeta(autoMeta(model2));
-
-console.log(formMetaDataObj);

+ 1 - 7
src/app/_mock/testfields.v4.ts

@@ -1,7 +1,5 @@
 // TESTS: Lazy combination of Automatic MetaData with Extra MetaData
 
-import { buildModeledMeta, autoMeta, combineExtraMeta } from './../dynaform/libs';
-
 const model2 = {
 	a: 'Value 1',
 	b: 'Value 2',
@@ -28,9 +26,5 @@ const extra2 = {
 	}
 };
 
+export { model2, extra2 };
 
-const auto2 = autoMeta(model2);
-const combined2 = combineExtraMeta(auto2, extra2);
-export const formMetaDataObj = buildModeledMeta(combined2);
-
-console.log(formMetaDataObj);

+ 35 - 0
src/app/_mock/testfields.v5.ts

@@ -0,0 +1,35 @@
+// TESTS: ADADADA
+
+const model = {
+	dynaformtest: {
+		a: 'Value 1',
+		b: 'Value 2',
+		c: 'Maybe',
+		d: {
+			e: 444,
+			f: 555,
+			g: {
+				h: true,
+				i: false
+			}
+		},
+		z: 'THE END'
+	}
+};
+
+const meta = {
+	dynaformtest: {
+		meta: {
+			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?' },
+					f: { type: 'datepicker' }
+				}
+			}
+		}
+	}
+};
+
+export { model, meta };

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

@@ -1,8 +1,8 @@
 import { Component, OnInit, ViewChild, TemplateRef } from '@angular/core';
-import { FormBuilder, FormGroup } from '@angular/forms';
-import { buildFormGroup } from './dynaform/libs';
+import { FormGroup } from '@angular/forms';
+import { DynaformService } from './dynaform/services/dynaform.service';
 
-import { formMetaDataObj } from './_mock/testfields.v4';
+import { model, meta } from './_mock/testfields.v5';
 
 @Component({
 	selector: 'app-root',
@@ -13,22 +13,14 @@ export class AppComponent implements OnInit {
 
 	form: FormGroup;
 
-	formMetaDataObj = formMetaDataObj;
-
 	@ViewChild('testTemplate', { read: TemplateRef })
 	private tref: TemplateRef<any>;
 
-	constructor(
-		protected fb: FormBuilder
-	) {
+	constructor(private dynaform: DynaformService) {
 	}
 
 	ngOnInit() {
-		const fullFormMeta = {
-			dynaformtest: { meta: this.formMetaDataObj }
-		};
-		console.log(fullFormMeta);
-		this.form = buildFormGroup(fullFormMeta);
+		this.form = this.dynaform.build(model, meta);
 		console.log(this.form);
 	}
 }

+ 4 - 1
src/app/dynaform/dynaform.module.ts

@@ -4,8 +4,8 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
 
 import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
 
-import { DynaformComponent } from './dynaform.component';
 import { DynafieldDirective } from './directives/dynafield.directive';
+import { DynaformService } from './services/dynaform.service';
 
 import * as formFieldComponents from './components';
 const ffcArr = Object.values(formFieldComponents); // Array of all the Form Field Components
@@ -25,6 +25,9 @@ import { DateInputsModule } from '@progress/kendo-angular-dateinputs';
 		...ffcArr
 	],
 	entryComponents: ffcArr,
+	providers: [
+		DynaformService
+	],
 	exports: ffcArr
 })
 export class DynaformModule { }

+ 93 - 0
src/app/dynaform/services/dynaform.service.ts

@@ -0,0 +1,93 @@
+/*
+
+Dynaform Service, exposing 8 public methods
+===========================================
+
+build(model, meta) - takes a model and (lazy)metadata and returns a FormGroup
+autoBuildFormGroup(model, meta) - synonym for autoForm
+autoBuildModeledMeta(model, meta) - takes a model and (lazy)metadata and returns expanded metadata
+
+buildFormGroup(metadata) - builds FormGroups from modelled metdata, recursively if necessary
+buildFieldSpecificMeta(metadata) - use field metadta models to fill out metadata
+combineModelWithMeta(model, extraMeta) - automatically generated metadata for model then combines extra metadata
+combineExtraMeta(metadata, extraMeta) - combine extra metadata into metatdata, lazyly and recursively
+autoMeta(model) - generate basic metadata from a raw or mapped model, recursively if necessary
+
+
+NOTES
+-----
+This class acts as am injectable wraper around the exports of meta-utils.ts,
+and creates a buildFormGroup function using the injected FormBuilder singleton
+
+
+USAGE
+-----
+
+TO ADD ...
+
+
+EXAMPLES
+--------
+
+Given ... TO ADD ...
+
+*/
+
+import { Injectable } from '@angular/core';
+import { FormBuilder, FormGroup } from '@angular/forms';
+import {
+	autoMeta, combineModelWithMeta, combineExtraMeta,
+	buildFieldSpecificMeta, buildFormGroupFunctionFactory
+} from './meta-utils';
+
+@Injectable()
+export class DynaformService {
+
+	protected buildFormGroup: (meta) => FormGroup;
+
+	constructor(private fb: FormBuilder) {
+		this.buildFormGroup = buildFormGroupFunctionFactory(fb);
+	}
+
+	// -----------------------------------------------------------------------------------------------------------------
+	// Convenience methods combining several steps
+
+	build(model, meta = {}): FormGroup {
+		// Shortname for autoBuildFormGroup
+		return this.autoBuildFormGroup(model, meta);
+	}
+
+	autoBuildFormGroup(model, meta = {}): FormGroup {
+		const modelWithMeta = this.autoBuildModeledMeta(model, meta);
+		return this.buildFormGroup(modelWithMeta);
+	}
+
+	autoBuildModeledMeta(model, meta = {}) {
+		const modelWithMeta = this.combineModelWithMeta(model, meta);
+		return this.buildFieldSpecificMeta(modelWithMeta);
+	}
+
+	// -----------------------------------------------------------------------------------------------------------------
+	// Build field-type-specific metadata using the form field models (see dynaform/models)
+
+	buildFieldSpecificMeta(meta) {
+		return buildFieldSpecificMeta(meta);
+	}
+
+	// -----------------------------------------------------------------------------------------------------------------
+	// Lower-level methods
+
+	combineModelWithMeta(model, meta) {
+		return combineModelWithMeta(model, meta);
+	}
+
+	combineExtraMeta(meta, extraMeta) {
+		return combineExtraMeta(meta, extraMeta);
+	}
+
+	autoMeta(model) {
+		return autoMeta(model);
+	}
+
+}
+

+ 81 - 75
src/app/dynaform/libs/index.ts

@@ -1,18 +1,17 @@
-import { FormBuilder, FormControl } from '@angular/forms';
+import { FormBuilder, FormGroup, FormControl } from '@angular/forms';
 import { reduce } from 'lodash/fp';
 import * as fmdModels from './../models';
-import { Container } from '@angular/compiler/src/i18n/i18n_ast';
 
 /*
- * FORM UTILITIES
+ * FORM METADATA UTILITIES
  *
  * Exports
  * -------
  * autoMeta(model) - generate basic metadata from a raw or mapped model, recursively if necessary
  * combineExtraMeta(metadata, extraMeta) - combine extra metadata into metatdata, lazyly and recursively
  * combineModelWithMeta(model, extraMeta) - automatically generated metadata for model then combines extra metadata
- * buildModedMeta(metadata) - use field metadta models to fill out metadata
- * buildFormGroup(metadata) - builds FormGroups from modelled metdata, recursively if necessary
+ * buildFieldSpecificMeta(metadata) - use field metadata models to expand metadata
+ * buildFormGroupFunctionFactory(fb) - return a function to buildFormGroups using the supplied FormBuilder singleton
  *
  * Variable names
  * --------------
@@ -22,40 +21,9 @@ import { Container } from '@angular/compiler/src/i18n/i18n_ast';
  *
  */
 
-const fb = new FormBuilder();
-
-// ---------------------------------------------------------------------------------------------------------------------
-// Helper Funstions
-
-const addProp = (obj, key, val) => { obj[key] = val; return obj; };
-
-// Is Group
-// Helper function to distinguish group from field
-const isGroup = metaFoG => metaFoG.meta;
-
-// Is Container
-// Helper function to distinguish container group (a group of child fields)
-const isContainer = metaFoG => isGroup(metaFoG) && (metaFoG.type === 'container' || typeof metaFoG.type === 'undefined');
-
-// ---------------------------------------------------------------------------------------------------------------------
-// Add Missing Names
-// Helper function to add any missing 'name' properties to Fields and Groups using property's key, recursively
-// MAYBE NOT NEEDED?
-
-const addNameIfMissing = (metaFoG, key) => metaFoG.name ? metaFoG : addProp(metaFoG, 'name', key);
-const addNameToSelfAndChildren = ( [key, metaFoG] ) => {
-	metaFoG = addNameIfMissing(metaFoG, key);
-	if (isGroup(metaFoG)) {
-		metaFoG.meta = addMissingNames(metaFoG.meta); // Recursion
-	}
-	return [key, metaFoG];
-};
-const addMissingNames = metaG => Object.entries(metaG)
-	.map(addNameToSelfAndChildren)
-	.reduce((res, [key, val]) => addProp(res, key, val), {});
-
 // ---------------------------------------------------------------------------------------------------------------------
 // Raw Model (or Mapped Raw Model) to Automatic Metadata
+// ---------------------------------------------------------------------------------------------------------------------
 
 const isScalar = val => typeof val === 'boolean' || typeof val === 'number' || typeof val === 'string';
 
@@ -69,13 +37,15 @@ const autoMeta = model => Object.entries(model)
 	.map(keyValPairToMetaRecursive)
 	.reduce((res, [key, val]) => addProp(res, key, val), {});
 
+
 // ---------------------------------------------------------------------------------------------------------------------
 // Combine automatically generated metadata with overrides
+// ---------------------------------------------------------------------------------------------------------------------
 
-const combineMetaForField = (metaF, metaFextra) => Object.assign(metaF, metaFextra);
-const combineExtraMeta = (metaG, metaExtra) => {
+const combineMetaForField = (metaF, extraMetaF) => Object.assign(metaF, extraMetaF);
+const combineExtraMeta = (metaG, extraMeta) => {
 	const combinedMeta = {};
-	Object.entries(metaExtra).forEach(([key, val]) => {
+	Object.entries(extraMeta).forEach(([key, val]) => {
 		if (typeof metaG[key] === 'object') {
 			combinedMeta[key] = (<any>val).meta ?
 				combineMetaForField(metaG[key], { meta: combineExtraMeta(metaG[key].meta, (<any>val).meta) }) :
@@ -85,15 +55,15 @@ const combineExtraMeta = (metaG, metaExtra) => {
 	return { ...metaG, ...combinedMeta };
 };
 
-// ---------------------------------------------------------------------------------------------------------------------
-// Combine model with overrides
+// Combine model with overrides (after automatically generating metadata from the model)
+const combineModelWithMeta = (model, extraMeta) => combineExtraMeta(autoMeta(model), extraMeta);
 
-const combineModelWithMeta = (model, metaExtra) => combineExtraMeta(autoMeta(model), metaExtra);
 
 // ---------------------------------------------------------------------------------------------------------------------
-// Build Modelled Metadata - Form Metadata Factory
+// Build Form-Field-Type-Specific Metadata (using the field models in dynaform/models)
+// ---------------------------------------------------------------------------------------------------------------------
 
-const buildClassName = (t = 'text') => {
+const buildFieldClassName = (t = 'text') => {
 	const start = t[0].toUpperCase() + t.slice(1);
 	if (start === 'Container' || t.slice(-5) === 'Group') {
 		return start;
@@ -103,7 +73,7 @@ const buildClassName = (t = 'text') => {
 
 const buildModeledField = metaFoG => {
 	const type = isContainer(metaFoG) ? 'container' : metaFoG.type;
-	const className = buildClassName(type);
+	const className = buildFieldClassName(type);
 	if (!fmdModels[className]) {
 		throw new Error(`No metadata model "${className}" for type "${type}"`);
 	}
@@ -111,50 +81,86 @@ const buildModeledField = metaFoG => {
 };
 
 // Build Form Group Member
-const buildModeledGroupMember = metaFoG => {
+const buildModeledFieldGroupMember = metaFoG => {
 	const modeledGroupMember = buildModeledField(metaFoG);
 	if (isContainer(metaFoG)) {
-		modeledGroupMember.meta = _buildModeledGroup(metaFoG.meta);
+		modeledGroupMember.meta = _buildModeledFieldGroup(metaFoG.meta);
 	}
 	return modeledGroupMember;
 };
 
 // Build Form Group
-const buildModeledGroupReducerIteree = (res, metaFoG) => Object.assign(res, { [metaFoG.name]: buildModeledGroupMember(metaFoG) });
-const _buildModeledGroup = metaG => reduce(buildModeledGroupReducerIteree, {}, metaG);
-const buildModeledMeta = metaG => _buildModeledGroup(metaG);
+const buildModeledFieldGroupReducerIteree = (res, metaFoG) => Object.assign(res, { [metaFoG.name]: buildModeledFieldGroupMember(metaFoG) });
+const _buildModeledFieldGroup = metaG => reduce(buildModeledFieldGroupReducerIteree, {}, metaG);
+const buildFieldSpecificMeta = metaG => _buildModeledFieldGroup(metaG);
+
 
 // ---------------------------------------------------------------------------------------------------------------------
-// Functions which build FormControls and FormGroups
-
-// Build Form Control
-// TODO: Flesh out function to build validators
-const buildControlState = metaF => ({ value: metaF.value || metaF.default, disabled: metaF.isDisabled });
-const buildValidators = metaF => ({
-	validators: null,
-	asyncValidators: null,
-	updateOn: 'blur'
-});
-const buildFormControl = metaF => new FormControl(buildControlState(metaF) /*, buildValidators(metaF) */);
+// Build FormGroups
+// ---------------------------------------------------------------------------------------------------------------------
 
-// Build Form Group Member
-const buildFormGroupMember = metaFoG => isGroup(metaFoG) ? _buildFormGroup(metaFoG.meta) : buildFormControl(metaFoG);
+const buildFormGroupFunctionFactory = (fb: FormBuilder): (meta) => FormGroup => {
+	// Establishes a closre over the supplied FormBuilder and returns a function that builds FormGroups from metadata
+	// ( it's done this way so we can use the FormBuilder singleton without reinitialising )
+
+	// Build Form Control
+	// TODO: Flesh out function to build validators
+	const buildControlState = metaF => ({ value: metaF.value || metaF.default, disabled: metaF.isDisabled });
+	const buildValidators = metaF => ({
+		validators: null,
+		asyncValidators: null,
+		updateOn: 'blur'
+	});
+	const buildFormControl = metaF => new FormControl(buildControlState(metaF) /*, buildValidators(metaF) */);
 
-// Build Form Group
-const buildFormGroupReducerIteree = (res, metaFoG) => Object.assign(res, { [metaFoG.name]: buildFormGroupMember(metaFoG) });
-const _buildFormGroup = metaG => fb.group(reduce(buildFormGroupReducerIteree, {}, metaG));
-const buildFormGroup = metaG => _buildFormGroup(addMissingNames(metaG));
+	// Build Form Group Member
+	const buildFormGroupMember = metaFoG => isGroup(metaFoG) ?
+		_buildFormGroup(metaFoG.meta) :
+		buildFormControl(metaFoG);
+
+	const buildFormGroupReducerIteree = (res, metaFoG) => Object.assign(res, { [metaFoG.name]: buildFormGroupMember(metaFoG) });
+	const _buildFormGroup = _metaG => fb.group(reduce(buildFormGroupReducerIteree, {}, _metaG));
 
+	const buildFormGroup = metaG => {
+		const metaWithNames = addMissingNames(metaG);
+		return _buildFormGroup(metaWithNames);
+	};
+	return buildFormGroup;
+};
+
+
+// ---------------------------------------------------------------------------------------------------------------------
+// Helper Funstions
 // ---------------------------------------------------------------------------------------------------------------------
-// Convenience Functions combining several steps
 
-const autoBuildModeledMeta = (model, metaExtra = {}) => buildModeledMeta(combineModelWithMeta(model, metaExtra));
-const autoBuildFormGroup = (model, metaExtra = {}) => buildFormGroup(autoBuildModeledMeta(model, metaExtra));
+// Add Property to object
+const addProp = (obj, key, val) => { obj[key] = val; return obj; };
+
+// Is Group
+// Helper function to distinguish group from field
+const isGroup = metaFoG => metaFoG.meta;
+
+// Is Container
+// Helper function to distinguish container group (a group of child fields)
+const isContainer = metaFoG => isGroup(metaFoG) && (metaFoG.type === 'container' || typeof metaFoG.type === 'undefined');
+
+// Add Missing Names
+// Helper function to add any missing 'name' properties to Fields and Groups using property's key, recursively
+const addNameIfMissing = (metaFoG, key) => metaFoG.name ? metaFoG : addProp(metaFoG, 'name', key);
+const addNameToSelfAndChildren = ( [key, metaFoG] ) => {
+	metaFoG = addNameIfMissing(metaFoG, key);
+	if (isGroup(metaFoG)) {
+		metaFoG.meta = addMissingNames(metaFoG.meta); // Recursion
+	}
+	return [key, metaFoG];
+};
+const addMissingNames = metaG => Object.entries(metaG)
+	.map(addNameToSelfAndChildren)
+	.reduce((res, [key, val]) => addProp(res, key, val), {});
+
 
 // ---------------------------------------------------------------------------------------------------------------------
 // Exports
+// ---------------------------------------------------------------------------------------------------------------------
 
-export {
-	autoMeta, combineModelWithMeta, combineExtraMeta, buildModeledMeta, buildFormGroup,
-	autoBuildModeledMeta, autoBuildFormGroup
-};
+export { autoMeta, combineModelWithMeta, combineExtraMeta, buildFieldSpecificMeta, buildFormGroupFunctionFactory };