Przeglądaj źródła

Updates after inital use in amp-dashboard

Richard Knight 6 lat temu
rodzic
commit
8c624092e2

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

@@ -10,7 +10,7 @@ export abstract class GroupInputComponent implements OnInit {
 	meta;
 
 	formGroup: FormGroup;
-	childMetaArray: Array<StringMap>;
+	childMetaArray: Array<StringMap<any>>;
 	controlNames: Array<string>;
 
 	exposeMetaInTemplate: string[] = [];

+ 1 - 1
src/app/dynaform/components/clarity/checkbox/clr-checkbox.component.html

@@ -1,5 +1,5 @@
 <clr-checkbox-wrapper>
 	<input type="checkbox" clrCheckbox [formControl]="control" [value]="value" (change)="toggleChecked($event)">
-	<label>{{ value }}</label>
+	<label>{{ label }}</label>
 </clr-checkbox-wrapper>
 

+ 2 - 0
src/app/dynaform/components/clarity/checkbox/clr-checkbox.component.ts

@@ -18,6 +18,7 @@ export class ClrCheckboxComponent extends CustomInputComponent implements OnInit
 
 	exposeMetaInTemplate: string[] = ['label', 'value', 'disabled', 'checkedValue'];
 
+	label: string;
 	value?: string | boolean = true;
 	isChecked: boolean;
 	disabled = false;
@@ -26,6 +27,7 @@ export class ClrCheckboxComponent extends CustomInputComponent implements OnInit
 	onChange: (val) => {};
 
 	ngOnInit() {
+		this.label = this.meta.label;
 		if (this.meta.disabled) {
 			this.control.disable();
 		}

+ 8 - 5
src/app/dynaform/components/clarity/select/clr-select.component.html

@@ -1,14 +1,17 @@
-<select *ngIf="!link; else fieldWithLink" clrSelect [formControl]="control">
-	<option *ngFor="let opt of options" [value]="opt.value">{{ opt.label }}</option>
-</select>
+<clr-select-container *ngIf="!link; else fieldWithLink">
+	<label>{{ label }}</label>
+	<select clrSelect [formControl]="control">
+		<option *ngFor="let opt of options" [value]="opt.value">{{ opt.label }}</option>
+	</select>
+</clr-select-container>
 
 <ng-template #fieldWithLink>
-	<div class="clr-input-group clr-input-group-sm">
+	<clr-select-container class="clr-input-group clr-input-group-sm">
 		<select [formControl]="control" clrSelect #field>
 			<option *ngFor="let opt of options" [value]="opt.value">{{ opt.label }}</option>
 		</select>
 		<div class="clr-input-group-append">
 			<button class="btn btn-outline-primary" type="button" (click)="navigate(field)">{{ link.label || 'Details' }}</button>
 		</div>
-	</div>
+	</clr-select-container>
 </ng-template>

+ 1 - 1
src/app/dynaform/components/clarity/select/clr-select.component.ts

@@ -9,7 +9,7 @@ import { Router, ActivatedRoute } from '@angular/router';
 })
 export class ClrSelectComponent extends NativeInputComponent {
 
-	exposeMetaInTemplate: string[] = ['options', 'link'];
+	exposeMetaInTemplate: string[] = ['label', 'options', 'link'];
 
 	constructor(private router: Router, private route: ActivatedRoute) {
 		super();

+ 4 - 2
src/app/dynaform/components/custom/checkbutton/checkbutton.component.ts

@@ -46,7 +46,9 @@ export class CheckbuttonComponent extends CustomInputComponent implements OnChan
 	}
 
 	public writeValue(value: any): void {
-		this.isChecked = value && value !== 0 && value !== 'false' ? value : false;
-		this.currentValue = this.isChecked ? value : false;
+		// this.isChecked = (value && value !== 0 && value !== 'false') ? true : false;
+		this.isChecked = !!value;
+		console.log(this.isChecked);
+		this.currentValue = this.isChecked ? this.value || this.checkedValue : false;
 	}
 }

+ 2 - 2
src/app/dynaform/components/nocontrol/button-group/button-group.component.ts

@@ -8,12 +8,12 @@ import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
 export class ButtonGroupComponent implements OnInit {
 
 	@Input()
-	meta: StringMap;
+	meta: StringMap<any>;
 
 	@Output()
 	call: EventEmitter<string> = new EventEmitter<string>();
 
-	buttons: StringMap[];
+	buttons: StringMap<any>[];
 
 	ngOnInit() {
 		this.buttons = this.meta.meta;

+ 2 - 2
src/app/dynaform/components/nocontrol/display/display.component.ts

@@ -8,10 +8,10 @@ import { Component, Input, OnInit } from '@angular/core';
 export class DisplayComponent implements OnInit {
 
 	@Input()
-	meta: StringMap;
+	meta: StringMap<any>;
 
 	value: string;
-	link?: StringMap;
+	link?: StringMap<any>;
 
 	ngOnInit() {
 		this.value = this.meta.value;

+ 1 - 1
src/app/dynaform/components/nocontrol/heading/heading.component.ts

@@ -8,7 +8,7 @@ import { Component, Input, OnInit } from '@angular/core';
 export class HeadingComponent implements OnInit {
 
 	@Input()
-	meta: StringMap;
+	meta: StringMap<any>;
 
 	text: string;
 	level: number;

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

@@ -14,7 +14,7 @@ import * as formFieldComponents from './../components';
 
 interface IFFC {
 	control: FormControl; // Remember, this can be an individual FormControl or a FormGroup
-	meta: StringMap;
+	meta: StringMap<any>;
 	propagateChange?: Function;
 	call?: EventEmitter<string>;
 }
@@ -30,7 +30,7 @@ const componentType = (type: string): string => type[0].toUpperCase() + type.sli
 export class DynafieldDirective extends NgControl implements OnInit, OnDestroy {
 
 	@Input()
-	meta: StringMap;
+	meta: StringMap<any>;
 
 	@Input()
 	set control(fc: FormControl) {

+ 8 - 19
src/app/dynaform/dynaform.component.html

@@ -9,19 +9,12 @@
 	<ng-container *ngIf="meta.type !== 'Hidden'">
 
 		<ng-container *ngIf="!meta.noLabel; else fullWidth">
-			<div class="form-group clr-row" *ngIf="isField(meta); else recursiveDynaform" [ngClass]="getRowClass(control, meta)">
-				<label class="clr-col-sm-4" [ngClass]="meta.class" [for]="meta.name" title="">
-					{{ meta.rowLabel ? meta.rowLabel : meta.label }}
-					<clr-tooltip *ngIf="control && control.touched && control.invalid">
-						<clr-icon clrTooltipTrigger shape="exclamation-circle" size="24" class="clr-error"></clr-icon>
-						<clr-tooltip-content clrPosition="top-right" clrSize="xs" *clrIfOpen>
-							<span [innerHTML]="getValidationFailureMessage(control, meta)"></span>
-						</clr-tooltip-content>
-					</clr-tooltip>
-				</label>
-				<div class="clr-col-sm-8">
-					<ng-container dynafield [control]="control" [meta]="meta" (call)="handleCallback($event)"></ng-container>
-				</div>
+			<div *ngIf="isField(meta); else recursiveDynaform" [ngClass]="getRowClass(control, meta)">
+				<ng-container dynafield
+					[control]="control"
+					[meta]="meta"
+					(call)="handleCallback($event)"
+				></ng-container>
 			</div>
 		</ng-container>
 
@@ -63,9 +56,7 @@
 		</ng-template>
 
 		<ng-template #fullWidth>
-			<div class="clr-row" [ngClass]="getRowClass(control, meta.type)">
-				<ng-container dynafield [control]="control" [meta]="meta" (call)="handleCallback($event)"></ng-container>
-			</div>
+			<ng-container dynafield [control]="control" [meta]="meta" (call)="handleCallback($event)"></ng-container>
 		</ng-template>
 		
 	</ng-container>
@@ -73,9 +64,7 @@
 
 
 <ng-template let-control="control" let-meta="meta" #dynaform>
-	<div *ngIf="meta.label" class="clr-row">
-		<h3 class="clr-col-sm-12 h-dyna" [ngClass]="'h-dyna-' + (path.length + 2)">{{ meta.label }}</h3>
-	</div>
+	<h3 *ngIf="meta.label" class="h-dyna" [ngClass]="'h-dyna-' + (path.length + 2)">{{ meta.label }}</h3>
 	<app-dynaform [formGroup]="control" [meta]="meta.meta" [template]="template" [ngClass]="{ 'dyna-hidden': !meta.focussed }" (call)="handleCallback($event)"></app-dynaform>
 </ng-template>
 

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

@@ -5,7 +5,7 @@ import { unwrapResolvedMetadata } from '@angular/compiler';
 
 export interface DynarowContext {
 	control: AbstractControl;
-	meta: StringMap;
+	meta: StringMap<any>;
 }
 
 @Component({
@@ -54,7 +54,7 @@ export class DynaformComponent implements OnInit {
 	@Output()
 	call: EventEmitter<string> = new EventEmitter<string>();
 
-	formMetaData: StringMap; // TODO: Tighten up type
+	formMetaData: StringMap<any>; // TODO: Tighten up type
 	controlNames: string[];
 	dynaFormRows: string[];
 	path: string[]; // path of current FormGroup - can be used to respond differently based on nesting level in template
@@ -124,11 +124,11 @@ export class DynaformComponent implements OnInit {
 		return path;
 	}
 
-	isField(meta: StringMap): boolean {
+	isField(meta: StringMap<any>): boolean {
 		return !meta.type.includes('Container');
 	}
 	
-	isRepeatingContainer(meta: StringMap): boolean {
+	isRepeatingContainer(meta: StringMap<any>): boolean {
 		return meta.type === 'RepeatingContainer';
 	}
 
@@ -148,7 +148,7 @@ export class DynaformComponent implements OnInit {
 		return result;
 	}
 
-	getRowClass(control: FormControl, meta: StringMap): string {
+	getRowClass(control: FormControl, meta: StringMap<any>): string {
 		const fieldTypeClass = meta.type ? meta.type.toLowerCase().replace('component', '') : '';
 		const fieldClass = Array.isArray(meta.class) ? meta.class.join(' ') : meta.class;
 		const containerClass = fieldClass ? ` container-${fieldClass}` : '';
@@ -190,7 +190,7 @@ export class DynaformComponent implements OnInit {
 
 	}
 
-	getValidationFailureMessage(control: FormControl, meta: StringMap) {
+	getValidationFailureMessage(control: FormControl, meta: StringMap<any>) {
 		if (control.errors) {
 			const errKeys = Object.keys(control.errors);
 			console.log(errKeys);
@@ -216,7 +216,7 @@ export class DynaformComponent implements OnInit {
 		// (identified by their 'noFormControsl' flag)
 		// e.g. ButtonGroups, HTMLChunks, etc.
 		return Object.entries(metadata)
-				.filter(([key, val]) => !(val as StringMap).noFormControls)
+				.filter(([key, val]) => !(val as StringMap<any>).noFormControls)
 				.reduce((acc, [key]) => [...acc, key], [])
 				.join(',');
 	}

+ 13 - 12
src/app/dynaform/models/field.model.ts

@@ -21,10 +21,10 @@ interface ISimpleFieldMetaData {
 	id?: string;							// CSS id to apply
 	before?: string;						// Ordering instruction - move before <name of another key in group>
 	after?: string;							// Ordering instruction - move after <name of another key in group>
-	disabled?: boolean;					// Whether field is initially disabled
+	disabled?: boolean;						// Whether field is initially disabled
 	validators?: ValidatorFn[];				// Array of validator functions - following Angular FormControl API
 	asyncValidators?: AsyncValidatorFn[];	// Array of async validator functions - following Angular FormControl API
-	valFailureMsgs?: StringMap;				// Validation failure messages - display appropriate message if validation fails
+	valFailureMsgs?: StringMap<any>;		// Validation failure messages - display appropriate message if validation fails
 	onChange?: (val) => {};					// Function to call when field's value changes
 }
 
@@ -52,7 +52,7 @@ interface IDropdownModifiedInputFieldMetaData extends ISimpleFieldMetaData {
 interface ITimePickerFieldMetaData extends ISimpleFieldMetaData {
 	value: Date | string;
 	format: string;
-	steps: StringMap;
+	steps: StringMap<any>;
 }
 
 // Utility to unCamelCase
@@ -81,7 +81,7 @@ abstract class SimpleField {
 	disabled = false;
 	validators: ValidatorFn[] = [];
 	asyncValidators: AsyncValidatorFn[] = [];
-	valFailureMsgs: StringMap = {};
+	valFailureMsgs: StringMap<any> = {};
 
 	constructor(meta: ISimpleFieldMetaData) {
 		if (meta.type === 'Multiline') {
@@ -114,7 +114,7 @@ class Option implements IOption {
 				this.label = opt.label;
 				this.value = opt.value;
 			}
-		} else if (typeof opt === 'string') {
+		} else {
 			this.label = opt;
 			this.value = opt;
 		}
@@ -125,8 +125,9 @@ abstract class OptionsField extends SimpleField {
 	options: Option[] = [];
 	constructor(meta: IOptionsFieldMetaData) {
 		super(meta);
-		if (Array.isArray(meta.options)) {
-			this.options = meta.options.reduce((acc, opt) => { acc.push(new Option(opt)); return acc; }, []);
+		let options = typeof meta.options === 'function' ? meta.options() : meta.options;
+		if (Array.isArray(options)) {
+			this.options = options.reduce((acc, opt) => { acc.push(new Option(opt)); return acc; }, []);
 		} else {
 			this.options = [
 				new Option({ label: 'Yes', value: true }),
@@ -268,12 +269,12 @@ class Container {
 	type = 'Container';
 	name: string;
 	label = '';
-	seed: StringMap;
+	seed: StringMap<any>;
 	template?: TemplateRef<any>;
 	button: string;	
 	focussed: boolean = true;	
-	meta: StringMap; // TODO: Tighten up on type with union type
-	constructor(containerMeta: StringMap) {
+	meta: StringMap<any>; // TODO: Tighten up on type with union type
+	constructor(containerMeta: StringMap<any>) {
 		Object.assign(this, containerMeta);
 		if (typeof this.label === 'undefined') {
 			this.label = unCamelCase(this.name);
@@ -286,7 +287,7 @@ class RepeatingContainer {
 	name: string;
 	prefix: string = 'group';
 	label = '';
-	seed: StringMap;
+	seed: StringMap<any>;
 	template?: TemplateRef<any>;
 	meta: Container[]; // An array of Containers
 	minRepeat: number = 1;
@@ -296,7 +297,7 @@ class RepeatingContainer {
 	showDeleteControl: boolean = true;
 	primaryField: string = '';
 	display: string = 'SINGLE'; // Display strategy to use  - ALL or SINGLE - All at once, or one at a time (with a switcher)
-	constructor(containerMeta: StringMap) {
+	constructor(containerMeta: StringMap<any>) {
 		Object.assign(this, containerMeta);
 		if (typeof this.label === 'undefined') {
 			this.label = unCamelCase(this.name);

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

@@ -129,7 +129,7 @@ const combineModelWithMeta = (model, extraMeta, createFromExtra = false) => comb
 
 // <--- Utilities supporting Repreating Containers --->
 
-const generateRepeatedGroup = (metaFoG, extraMeta, baseObjWithAllKeys): StringMap[] => {
+const generateRepeatedGroup = (metaFoG, extraMeta, baseObjWithAllKeys): StringMap<any>[] => {
 	// Calculate the number of repeats
 	const repeatInAutoMeta = Array.isArray(metaFoG.meta) ? metaFoG.meta.length : 0;
 	const repeatInExtraMeta = extraMeta['initialRepeat'] || extraMeta['minRepeat'];
@@ -146,7 +146,7 @@ const generateRepeatedGroup = (metaFoG, extraMeta, baseObjWithAllKeys): StringMa
 }
 
 // Get Repeating Container Base Object With All Keys
-const getRCBaseObjectWithAllKeys = (metaFoG, extraMeta, createFromExtra = false): StringMap => {
+const getRCBaseObjectWithAllKeys = (metaFoG, extraMeta, createFromExtra = false): StringMap<any> => {
 	// If creating from extra, make sure all group members have all keys in both model and meta (as this is a repeating group)
 	const keysFromModel = isArray(metaFoG.meta) && metaFoG.meta.length ? Object.keys(metaFoG.meta[0].meta) : [];
 	const keysFromExtraMeta = extraMeta['meta'] && extraMeta['meta'][0] ? Object.keys(extraMeta['meta'][0]) : [];
@@ -160,7 +160,7 @@ const getRCBaseObjectWithAllKeys = (metaFoG, extraMeta, createFromExtra = false)
 // Build Form-Field-Type-Specific Metadata (using the field models in dynaform/models)
 // ---------------------------------------------------------------------------------------------------------------------
 
-const resolveType = (metaFoG: StringMap): string => {
+const resolveType = (metaFoG: StringMap<any>): string => {
 	if (metaFoG.type) {
 		return metaFoG.type;
 	}
@@ -181,18 +181,18 @@ const buildFieldClassName = (t: string): string => {
 	return start + 'Field';
 };
 
-const buildModeledField = metaFoG => {
+const buildModeledField = (metaFoG, context) => {
 	const type = resolveType(metaFoG);
 	const className = buildFieldClassName(type);
 	if (!fmdModels[className]) {
 		throw new Error(`No metadata model "${className}" for type "${type}"`);
 	}
-	return new fmdModels[className](metaFoG);
+	return new fmdModels[className](metaFoG, context);
 };
 
 // Build Form Group Member
-const buildModeledFieldGroupMember = metaFoG => {
-	const modeledGroupMember = buildModeledField(metaFoG);
+const buildModeledFieldGroupMember = (metaFoG, context = {}) => {
+	const modeledGroupMember = buildModeledField(metaFoG, context);
 	if (isContainer(metaFoG)) {
 		modeledGroupMember.meta = _buildFieldSpecificMeta(modeledGroupMember.meta);
 	} else if (isRepeatingContainer(metaFoG)) {
@@ -229,7 +229,7 @@ const isAbsPath = path => typeof path === 'string' && path[0] === '/';
 const isRootPath = path => path === '/';
 const processPath = (parentPath, path) => isAbsPath(path) ? path : `${parentPath}.${path}`;
 
-const prependParentPathRecursive = (parentPath: string, obj: StringMap) => {
+const prependParentPathRecursive = (parentPath: string, obj: StringMap<any>) => {
 	return Object.entries(obj)
 		.map( ([key, mapping] ) => {
 			let mappingRes;
@@ -422,7 +422,7 @@ const insertAfter = (obj, afterKey, key, val = null) => {
 };
 
 // Process reordeing instructions recursively
-const _execMetaReorderingInstructions = (metaG: StringMap) => {
+const _execMetaReorderingInstructions = (metaG: StringMap<any>) => {
 	let reorderedGroup = { ...metaG };
 	Object.entries(metaG).forEach(([key, metaFoG]) => {
 		if (metaFoG.before) {
@@ -437,7 +437,7 @@ const _execMetaReorderingInstructions = (metaG: StringMap) => {
 	return reorderedGroup;
 };
 
-const execMetaReorderingInstructions = (metaG: StringMap) => {
+const execMetaReorderingInstructions = (metaG: StringMap<any>) => {
 	// Repeating Containers (which have array meta *at this point*) can't be reordered, but other types of containers can
 	return Array.isArray(metaG) ? cloneDeep(metaG) : _execMetaReorderingInstructions(cloneDeep(metaG));
 };

+ 27 - 13
src/app/dynaform/services/dynaform.service.ts

@@ -59,6 +59,12 @@
  * Typically you'd supply the component class instance, so that 'this' used in callbacks refers to the host component.
  *
  *
+ * DATA IN & DATA OUT
+ * ------------------
+ * updateForm - patch form values in FormGroup
+ * buildNewModel
+ *
+ *
  * LOWER-LEVEL METHODS
  * -------------------
  *
@@ -93,10 +99,11 @@ import {
 	autoMeta, combineModelWithMeta, combineExtraMeta, execMetaReorderingInstructions,
 	buildFieldSpecificMeta, extractFieldMappings, buildFormGroupFunctionFactory, generateNewModel
 } from './_formdata-utils';
+import { formArrayNameProvider } from '@angular/forms/src/directives/reactive_directives/form_group_name';
 
 export interface IFormAndMeta {
 	form: FormGroup;
-	meta: StringMap;
+	meta: StringMap<any>;
 }
 
 export interface ICallbacks {
@@ -106,6 +113,9 @@ export interface ICallbacks {
 @Injectable()
 export class DynaformService {
 
+	public form: FormGroup;
+	public meta: StringMap<any>;
+
 	public buildFormGroup: (meta) => FormGroup;
 	private buildStrategy: 'MODELFIRST' | 'METAFIRST' = 'MODELFIRST'; // Make ENUM type
 	private callbacks: ICallbacks = {};
@@ -127,9 +137,11 @@ export class DynaformService {
 		}
 	}
 
-	build(model: StringMap, meta = {}, createFromMeta = false): IFormAndMeta {
-		// Short name for autoBuildFormGroupAndMeta
-		return this.autoBuildFormGroupAndMeta(model, meta, createFromMeta);
+	build(model: StringMap<any>, meta = {}, createFromMeta = false): IFormAndMeta {
+		// Executes autoBuildFormGroupAndMeta and stores the result
+		const result = this.autoBuildFormGroupAndMeta(model, meta, createFromMeta);
+		({ form: this.form, meta: this.meta } = result);
+		return result;
 	}
 
 	register(callbacks: ICallbacks, cref: ComponentRef<any>['instance']) {
@@ -155,17 +167,19 @@ export class DynaformService {
 		}
 	}
 
-	updateForm(newModel: StringMap, form: FormGroup, meta: StringMap): void {
-		const mapping = extractFieldMappings(meta); // Memoize
+	updateForm(newModel: StringMap<any>, form?: FormGroup, meta?: StringMap<any>): void {
+		const mapping = extractFieldMappings(meta || this.meta); // Memoize
 		const mappedModel = this.modelMapper.reverseMap(newModel, mapping);
-		form.patchValue(mappedModel);
+		console.log('MAPPED MODEL', mappedModel);
+		(form || this.form).patchValue(mappedModel);
+		setTimeout(() => console.log(form || this.form));
 	}
 
-	buildNewModel(originalModel: StringMap, formVal: StringMap, meta: StringMap): StringMap {
+	buildNewModel(originalModel: StringMap<any>, formVal?: StringMap<any>, meta?: StringMap<any>): StringMap<any> {
 		console.log('%c *** buildNewModel *** ', this.conGreen);
-		const mapping = extractFieldMappings(meta); // Memoize
+		const mapping = extractFieldMappings(meta || this.meta); // Memoize
 		console.dir(mapping);
-		const updates = this.modelMapper.forwardMap(formVal, mapping);
+		const updates = this.modelMapper.forwardMap(formVal || this.form.value, mapping);
 		console.log('%c *** Updates *** ', this.conGreen);
 		console.dir(updates);
 		return generateNewModel(originalModel, updates);
@@ -174,7 +188,7 @@ export class DynaformService {
 	// -----------------------------------------------------------------------------------------------------------------
 	// Convenience methods combining several steps
 
-	autoBuildFormGroupAndMeta(model: StringMap, meta = {}, createFromMeta = false): IFormAndMeta {
+	autoBuildFormGroupAndMeta(model: StringMap<any>, meta = {}, createFromMeta = false): IFormAndMeta {
 		let _model;
 		if (this.buildStrategy === 'MODELFIRST') {
 			_model = model;
@@ -197,7 +211,7 @@ export class DynaformService {
 		};
 	}
 
-	autoBuildModeledMeta(model: StringMap, meta = {}, createFromMeta = false) {
+	autoBuildModeledMeta(model: StringMap<any>, meta = {}, createFromMeta = false) {
 		const modelWithMeta = this.combineModelWithMeta(model, meta, createFromMeta);
 		const reorderedMeta = execMetaReorderingInstructions(modelWithMeta);
 		return this.buildFieldSpecificMeta(reorderedMeta);
@@ -213,7 +227,7 @@ export class DynaformService {
 	// -----------------------------------------------------------------------------------------------------------------
 	// Lower-level methods
 
-	combineModelWithMeta(model: StringMap, meta, createFromMeta = false) {
+	combineModelWithMeta(model: StringMap<any>, meta, createFromMeta = false) {
 		return combineModelWithMeta(model, meta, createFromMeta);
 	}
 

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

@@ -105,12 +105,12 @@ const mapping = {
 */
 
 import { Injectable } from '@angular/core';
-import * as _ from 'lodash';
+import { get, set, unset, cloneDeep, merge, every, findLast, sortBy } from 'lodash';
 
 @Injectable()
 export class ModelMapperService {
 
-	mapping: StringMap;
+	mapping: StringMap<any>;
 	errors: string[] = [];
 
 	debug = false;
@@ -118,21 +118,21 @@ export class ModelMapperService {
 
 	constructor() { }
 
-	public setMapping(mapping: StringMap) {
+	public setMapping(mapping: StringMap<any>) {
 		this.mapping = mapping;
 	}
 
 	public forwardMap = (
-		model: StringMap,
-		mapping: StringMap = this.mapping,
+		model: StringMap<any>,
+		mapping: StringMap<any> = this.mapping,
 		mapMissing = false,
 		res = {},
 		stack = []
-	): StringMap => {
+	): StringMap<any> => {
 		// Map the input model onto res using the supplied mapping
 		Object.keys(model).forEach(key => {
 			const absPath = [...stack, key].join('.');
-			const _mapping = _.get(mapping, key, mapMissing ? key : false);
+			const _mapping = get(mapping, key, mapMissing ? key : false);
 			if (_mapping) {
 				const mappingType = this.resolveMappingType(_mapping);
 				switch(mappingType) {
@@ -155,7 +155,7 @@ export class ModelMapperService {
 								this.clog('Target Path', targetPath);
 								const func = storedContainerMapping.find(m => typeof m === 'function');
 								const funcRes = func(model[key]);
-								const fullRes = targetPath ? _.set({}, targetPath, funcRes) : funcRes; // Construct an update object from the model's root
+								const fullRes = targetPath ? set({}, targetPath, funcRes) : funcRes; // Construct an update object from the model's root
 								this.forwardMap(fullRes, {}, true, res);
 							} catch (e) {
 								this.clog(e);
@@ -184,23 +184,23 @@ export class ModelMapperService {
 	}
 
 	public lazyForwardMap = (
-		model: StringMap,
-		mapping: StringMap = this.mapping
-	): StringMap => this.forwardMap(model, mapping, true)
+		model: StringMap<any>,
+		mapping: StringMap<any> = this.mapping
+	): StringMap<any> => this.forwardMap(model, mapping, true)
 
 	public reverseMap = (
-		model: StringMap,
-		mapping: StringMap = this.mapping,
+		model: StringMap<any>,
+		mapping: StringMap<any> = this.mapping,
 		mapMissing = false,
 		pathsToDelete = [],
 		stack = []
-	): StringMap => {
+	): StringMap<any> => {
 		// 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');
 		}
 		const res = {};
-		const modelClone = stack.length ? model : _.cloneDeep(model); // Clone the model unless inside a recursive call
+		const modelClone = stack.length ? model : cloneDeep(model); // Clone the model unless inside a recursive call
 		Object.keys(mapping).filter(key => key !== '__').forEach(key => {
 			const dataMapping = mapping[key];
 			const mappingType = this.resolveMappingType(dataMapping);
@@ -208,7 +208,7 @@ export class ModelMapperService {
 				case 'simple':
 					{
 						// A simple path
-						const value = _.get(modelClone, dataMapping);
+						const value = get(modelClone, dataMapping);
 						if (typeof value !== 'undefined') {
 							this.deepSet(res, key, value);
 						}
@@ -251,9 +251,9 @@ export class ModelMapperService {
 			const deepestPathsLast = this.sortByPathDepth(pathsToDelete);
 			while(deepestPathsLast.length) {
 				const path = deepestPathsLast.pop();
-				const t = typeof _.get(modelClone, path);
+				const t = typeof get(modelClone, path);
 				if (t === 'number' || t === 'string' || t === 'boolean') {
-					_.unset(modelClone, path);
+					unset(modelClone, path);
 				}
 			}
 			const modelRemainder = this.deepCleanse(modelClone);
@@ -265,9 +265,9 @@ export class ModelMapperService {
 	}
 
 	public lazyReverseMap = (
-		model: StringMap,
-		mapping: StringMap = this.mapping
-	): StringMap => this.reverseMap(model, mapping, true)
+		model: StringMap<any>,
+		mapping: StringMap<any> = this.mapping
+	): StringMap<any> => this.reverseMap(model, mapping, true)
 
 	public getErrors() {
 		return this.errors;
@@ -285,12 +285,12 @@ export class ModelMapperService {
 			if (
 				Array.isArray(mappingPath)
 				&& mappingPath.length === 2
-				&& _.every(mappingPath, m => typeof m === 'function')
+				&& every(mappingPath, m => typeof m === 'function')
 				||
 				Array.isArray(mappingPath)
 				&& mappingPath.length === 3
 				&& (typeof mappingPath[0] === 'number' || typeof mappingPath[0] === 'string')
-				&& _.every(mappingPath.slice(1), m => typeof m === 'function')
+				&& every(mappingPath.slice(1), m => typeof m === 'function')
 			) {
 				mappingType = 'functional';
 			} else {
@@ -309,9 +309,9 @@ export class ModelMapperService {
 		if (path === '/') {
 			arg = model; // '/' indicates use the entire model
 		} else {
-			arg = _.get(model, path.replace(/^\//, ''));
+			arg = get(model, path.replace(/^\//, ''));
 		}
-		const func = _.findLast(fnMapping, m => typeof m === 'function');
+		const func = findLast(fnMapping, m => typeof m === 'function');
 		try {
 			result = func(arg);
 		} catch(e) {
@@ -324,25 +324,25 @@ export class ModelMapperService {
 		// NOTE: This mutates the incoming object at the moment, so doesn't reed to return a value
 		// Maybe rewrite with a more functional approach?
 		// Will deep merge where possible
-		const currentVal = _.get(obj, mappingPath);
+		const currentVal = get(obj, mappingPath);
 		const t = typeof currentVal;
 		if (t === 'undefined') {
-			_.set(obj, mappingPath, valueToSet);
+			set(obj, mappingPath, valueToSet);
 		} else if (t === 'number' || t === 'string' || t === 'boolean') {
 			// We can only overwrite existing scalar values, not deep merge
 			if (overwrite) {
 				this.errors.push('WARNING: Overwriting scalar value at', mappingPath);
-				_.set(obj, mappingPath, valueToSet);
+				set(obj, mappingPath, valueToSet);
 			} else {
 				this.errors.push('WARNING: Discarding scalar value at', mappingPath, 'as exisiting non-scalar value would be overwritten');
 			}
 		} else if (t === 'object' && typeof valueToSet === 'object') {
 			// Deep merge
-			let merged = _.merge(currentVal, valueToSet);
+			let merged = merge(currentVal, valueToSet);
 			if (!overwrite) {
-				merged = _.merge(merged, currentVal); // Is there a better way?
+				merged = merge(merged, currentVal); // Is there a better way?
 			}
-			_.set(obj, mappingPath, merged);
+			set(obj, mappingPath, merged);
 		} else {
 			this.errors.push('WARNING: Could not merge', typeof valueToSet, 'with object');
 		}
@@ -367,7 +367,7 @@ export class ModelMapperService {
 		return cleansedObj;
 	}
 
-	private sortByPathDepth = pathArr => _.sortBy(pathArr, p => p.split('.').length);
+	private sortByPathDepth = pathArr => sortBy(pathArr, p => p.split('.').length);
 
 	private clearErrors() {
 		this.errors = [];

+ 10 - 10
src/app/dynaform/testdata/testset.1.ts

@@ -119,16 +119,16 @@ in the house`
 }
 
 // ---------------------------------------------------------------------------------------------------------------------
-// Kendo
+// Clarity
 
-const timepicker = {
-	type: 'timepicker',
-	// value: new Date(1999, 10, 11, 12, 30, 45)
-	value: "12:45"
-};
+// const timepicker = {
+// 	type: 'timepicker',
+// 	// value: new Date(1999, 10, 11, 12, 30, 45)
+// 	value: "12:45"
+// };
 
-const datepicker = {
-	type: 'datepicker'
+const clrdatepicker = {
+	type: 'clrDatepicker'
 };
 
 // ---------------------------------------------------------------------------------------------------------------------
@@ -174,8 +174,8 @@ const meta = {
 	checkbutton,
 	dropdownModifiedInput,
 	checkbuttonGroup,
-	timepicker,
-	datepicker,
+	// timepicker,
+	clrdatepicker,
 	container,
 	multiline
 };

+ 1 - 1
src/app/dynaform/utils.ts

@@ -46,7 +46,7 @@ const arrayToMeta = array => array.map(val => ({ name: val, 'value' : val }));
 
 // Exclude 'fieldsToExclude' from obj, returning a new object
 // fieldsToExclude can be an array of field keys or a CSV of field keys
-const excludeFields = (obj: StringMap, fieldsToExclude: string | string[]) => {
+const excludeFields = (obj: StringMap<any>, fieldsToExclude: string | string[]) => {
 	const ex = Array.isArray(fieldsToExclude) ? fieldsToExclude : fieldsToExclude.split(',').map(key => key.trim());
 	return Object.entries(obj).reduce(
 		(res, [key, val]) => ex.includes(key) ? res : { ...res, [key]: val },