Bläddra i källkod

Upgrade to Angular 7, and sync with LMP version

Richard Knight 6 år sedan
förälder
incheckning
c896ddca91
26 ändrade filer med 2226 tillägg och 1526 borttagningar
  1. 0 11
      dynaform.code-workspace
  2. 2036 1390
      package-lock.json
  3. 31 31
      package.json
  4. 1 1
      src/app/_mock/testfields.v1.ts
  5. 1 1
      src/app/_mock/testfields.v2.ts
  6. 1 1
      src/app/dynaform/components/custom/checkbutton/checkbutton.component.html
  7. 4 4
      src/app/dynaform/components/custom/checkbutton/checkbutton.component.ts
  8. 1 1
      src/app/dynaform/components/custom/multiline/multiline.component.ts
  9. 2 2
      src/app/dynaform/components/group/checkbutton-group/checkbutton-group.component.html
  10. 3 3
      src/app/dynaform/components/group/checkbutton-group/checkbutton-group.component.ts
  11. 1 0
      src/app/dynaform/components/index.ts
  12. 1 1
      src/app/dynaform/components/native/select/select.component.ts
  13. 2 2
      src/app/dynaform/components/nocontrol/button-group/button-group.component.ts
  14. 11 0
      src/app/dynaform/components/nocontrol/display/display.component.html
  15. 0 0
      src/app/dynaform/components/nocontrol/display/display.component.scss
  16. 25 0
      src/app/dynaform/components/nocontrol/display/display.component.spec.ts
  17. 20 0
      src/app/dynaform/components/nocontrol/display/display.component.ts
  18. 9 9
      src/app/dynaform/directives/dynafield.directive.ts
  19. 6 4
      src/app/dynaform/dynaform.component.ts
  20. 32 20
      src/app/dynaform/models/field.model.ts
  21. 9 7
      src/app/dynaform/services/_formdata-utils.ts
  22. 15 11
      src/app/dynaform/services/dynaform.service.ts
  23. 6 5
      src/app/dynaform/services/model-mapper.service.ts
  24. 1 1
      src/app/dynaform/testdata/testset.1.ts
  25. 8 8
      src/app/dynaform/testdata/testset.4.ts
  26. 0 13
      src/ng-dynaform.code-workspace

+ 0 - 11
dynaform.code-workspace

@@ -1,11 +0,0 @@
-{
-	"folders": [
-		{
-			"path": "src/app/dynaform"
-		},
-		{
-			"path": "src"
-		}
-	],
-	"settings": {}
-}

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 2036 - 1390
package-lock.json


+ 31 - 31
package.json

@@ -12,49 +12,49 @@
   },
   "private": true,
   "dependencies": {
-    "@angular/animations": "^6.1.7",
-    "@angular/common": "^6.1.7",
-    "@angular/compiler": "^6.1.7",
-    "@angular/core": "^6.1.7",
-    "@angular/forms": "^6.1.7",
-    "@angular/http": "^6.1.7",
-    "@angular/platform-browser": "^6.1.7",
-    "@angular/platform-browser-dynamic": "^6.1.7",
-    "@angular/router": "^6.1.7",
-    "@ng-bootstrap/ng-bootstrap": "^3.2.0",
-    "@progress/kendo-angular-dateinputs": "^3.4.4",
-    "@progress/kendo-angular-intl": "^1.5.0",
-    "@progress/kendo-angular-l10n": "^1.2.0",
-    "@progress/kendo-theme-bootstrap": "^2.14.0",
+    "@angular/animations": "^7.1.1",
+    "@angular/common": "^7.1.1",
+    "@angular/compiler": "^7.1.1",
+    "@angular/core": "^7.1.1",
+    "@angular/forms": "^7.1.1",
+    "@angular/http": "^7.1.1",
+    "@angular/platform-browser": "^7.1.1",
+    "@angular/platform-browser-dynamic": "^7.1.1",
+    "@angular/router": "^7.1.1",
+    "@ng-bootstrap/ng-bootstrap": "^4.0.0",
+    "@progress/kendo-angular-dateinputs": "^3.5.1",
+    "@progress/kendo-angular-intl": "^1.6.1",
+    "@progress/kendo-angular-l10n": "^1.3.0",
+    "@progress/kendo-theme-bootstrap": "^2.17.0",
     "angular-super-validator": "^2.0.0",
     "core-js": "^2.5.7",
     "json-formatter-js": "^2.2.1",
     "lodash": "^4.17.11",
-    "rxjs": "^6.3.2",
-    "rxjs-compat": "^6.3.2",
+    "rxjs": "^6.3.3",
+    "rxjs-compat": "^6.3.3",
     "zone.js": "^0.8.26"
   },
   "devDependencies": {
-    "@angular-devkit/build-angular": "~0.8.2",
-    "@angular/cli": "~6.2.2",
-    "@angular/compiler-cli": "^6.1.7",
-    "@angular/language-service": "^6.1.7",
-    "@types/jasmine": "~2.8.8",
-    "@types/jasminewd2": "~2.0.3",
-    "@types/lodash": "^4.14.116",
-    "@types/node": "~10.10.1",
-    "codelyzer": "^4.4.4",
-    "jasmine-core": "~3.2.1",
+    "@angular-devkit/build-angular": "~0.11.0",
+    "@angular/cli": "~7.1.0",
+    "@angular/compiler-cli": "^7.1.1",
+    "@angular/language-service": "^7.1.1",
+    "@types/jasmine": "~3.3.0",
+    "@types/jasminewd2": "~2.0.6",
+    "@types/lodash": "^4.14.118",
+    "@types/node": "~10.12.11",
+    "codelyzer": "^4.5.0",
+    "jasmine-core": "~3.3.0",
     "jasmine-spec-reporter": "~4.2.1",
-    "karma": "~3.0.0",
+    "karma": "~3.1.3",
     "karma-chrome-launcher": "~2.2.0",
     "karma-coverage-istanbul-reporter": "^2.0.4",
-    "karma-jasmine": "~1.1.2",
-    "karma-jasmine-html-reporter": "^1.3.1",
+    "karma-jasmine": "~2.0.1",
+    "karma-jasmine-html-reporter": "^1.4.0",
     "protractor": "^5.4.1",
-    "rxjs-tslint": "^0.1.5",
+    "rxjs-tslint": "^0.1.6",
     "ts-node": "~7.0.1",
     "tslint": "~5.11.0",
-    "typescript": "~2.9.2"
+    "typescript": "3.1.6"
   }
 }

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

@@ -42,7 +42,7 @@ const radioField = new fmd.RadioField({
 const disabledTextField = new fmd.TextField({
 	name: 'disabledTextField',
 	placeholder: 'You can\'t touch this',
-	isDisabled: true
+	disabled: true
 });
 
 const radioFieldHorizontal = new fmd.RadioField({

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

@@ -41,7 +41,7 @@ const radioField = {
 const disabledTextField = {
 	type: 'Text',
 	placeholder: 'You can\'t touch this',
-	isDisabled: true
+	disabled: true
 };
 
 const radioFieldHorizontal = {

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

@@ -3,7 +3,7 @@
 	[ngClass]="{
 		'btn-success': isChecked,
 		'lmp-btn-greyed': !isChecked,
-		'lmp-btn-disabled': isDisabled
+		'lmp-btn-disabled': disabled
 	}"
 	href
 	>

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

@@ -16,23 +16,23 @@ import { CustomInputComponent } from './../../_abstract/custom-input.component';
 })
 export class CheckbuttonComponent extends CustomInputComponent implements OnChanges {
 
-	exposeMetaInTemplate: string[] = ['label', 'value', 'isDisabled', 'checkedValue', 'onChange'];
+	exposeMetaInTemplate: string[] = ['label', 'value', 'disabled', 'checkedValue', 'onChange'];
 
 	value?: string | boolean = true;
 	isChecked: boolean;
-	isDisabled = false;
+	disabled = false;
 	currentValue: string | boolean;
 	checkedValue: string | boolean = true;
 	onChange: (val) => {};
 
 	ngOnChanges() {
-		this.isDisabled = this.meta.isDisabled;
+		this.disabled = this.meta.disabled;
 	}
 
 	private toggleChecked(e): void {
 		e.target.blur();
 		e.preventDefault();
-		if (this.isDisabled) { return; }
+		if (this.disabled) { return; }
 		this.isChecked = !this.isChecked;
 		this.currentValue = this.isChecked ? this.value || this.checkedValue : false;
 		if (typeof this.onChange === 'function') {

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

@@ -1,5 +1,5 @@
 import { Component, forwardRef } from '@angular/core';
-import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
+import { NG_VALUE_ACCESSOR } from '@angular/forms';
 import { CustomInputComponent } from './../../_abstract/custom-input.component';
 
 @Component({

+ 2 - 2
src/app/dynaform/components/group/checkbutton-group/checkbutton-group.component.html

@@ -1,11 +1,11 @@
-<div class="lmp-checkbutton-group btn-group btn-group-toggle" [formGroup]="formGroup">
+<div class="lmp-checkbutton-group" [formGroup]="formGroup">
 	<app-checkbutton *ngFor="let groupMember of controlNames; let i = index"
 		[formControlName]="groupMember"
 		[meta]="childMetaArray[i]"
 	>
 	</app-checkbutton>
 </div>
-<div *ngIf="showAllOrNone">
+<div *ngIf="showAllOrNone" class="buttongroup">
 	<a (click)="selectAll($event)" href class="btn btn-sm btn-outline-primary">Select All</a> 
 	<a (click)="selectNone($event)" href class="btn btn-sm btn-outline-primary">Select None</a>
 </div>

+ 3 - 3
src/app/dynaform/components/group/checkbutton-group/checkbutton-group.component.ts

@@ -30,8 +30,8 @@ export class CheckbuttonGroupComponent extends GroupInputComponent implements On
 		}
 		if (this.firstEnablesRest) {
 			this.firstControl = this.formGroup.controls[this.controlNames[0]] as FormControl;
-			this.childMetaArray[0].isDisabled = false;
-			this.childMetaArray.slice(1).map(meta => { meta.isDisabled = !this.firstControl.value; return meta; });
+			this.childMetaArray[0].disabled = false;
+			this.childMetaArray.slice(1).map(meta => { meta.disabled = !this.firstControl.value; return meta; });
 
 			// Observe value changes on first control
 			this.firstControl.valueChanges.subscribe(val => {
@@ -40,7 +40,7 @@ export class CheckbuttonGroupComponent extends GroupInputComponent implements On
 					// See https://juristr.com/blog/2016/04/angular2-change-detection/
 					this.childMetaArray[i] = {
 						...this.childMetaArray[i],
-						isDisabled: !val
+						disabled: !val
 					};
 				}
 			});

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

@@ -16,3 +16,4 @@ export { TimepickerComponent } from './kendo/timepicker/timepicker.component';
 export { DatepickerComponent } from './kendo/datepicker/datepicker.component';
 export { ButtonGroupComponent } from './nocontrol/button-group/button-group.component';
 export { HeadingComponent } from './nocontrol/heading/heading.component';
+export { DisplayComponent } from './nocontrol/display/display.component';

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

@@ -17,7 +17,7 @@ export class SelectComponent extends NativeInputComponent {
 	
 	navigate(field: HTMLSelectElement) {
 		const base = Array.isArray(this.meta.link.route) ? this.meta.link.route : [this.meta.link.route];
-		const destination = [...base, field[field.selectedIndex].value];
+		const destination = [...base, field.options[field.selectedIndex].value];
 		this.router.navigate(destination, { relativeTo: this.route });
 	}
 

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

@@ -12,13 +12,13 @@ export class ButtonGroupComponent implements OnInit {
 
 	@Output()
 	call: EventEmitter<string> = new EventEmitter<string>();
-	
+
 	buttons: StringMap[];
 
 	ngOnInit() {
 		this.buttons = this.meta.meta;
 	}
-	
+
 	handle(fnId: string, e: Event) {
 		e.preventDefault();
 		(e.target as HTMLElement).blur();

+ 11 - 0
src/app/dynaform/components/nocontrol/display/display.component.html

@@ -0,0 +1,11 @@
+<span *ngIf="!link; else displayWithLink">{{ value }}</span>
+
+<ng-template #displayWithLink>
+	<div class="input-group input-group-sm">
+		<span>{{ value }}</span>
+		<div class="input-group-append">
+			<button class="btn btn-outline-primary" type="button"
+				[routerLink]="[ link.route, value ]">{{ link.label || 'Details' }}</button>
+		</div>
+	</div>
+</ng-template>

+ 0 - 0
src/app/dynaform/components/nocontrol/display/display.component.scss


+ 25 - 0
src/app/dynaform/components/nocontrol/display/display.component.spec.ts

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

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

@@ -0,0 +1,20 @@
+import { Component, Input, OnInit } from '@angular/core';
+
+@Component({
+	selector: 'app-display',
+	templateUrl: './display.component.html',
+	styleUrls: ['./display.component.scss']
+})
+export class DisplayComponent implements OnInit {
+
+	@Input()
+	meta: StringMap;
+
+	value: string;
+	link?: StringMap;
+
+	ngOnInit() {
+		this.value = this.meta.value;
+		this.link = this.meta.link;
+	}
+}

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

@@ -12,13 +12,13 @@ import {
 
 import * as formFieldComponents from './../components';
 
-interface FFC {
+interface IFFC {
 	control: FormControl; // Remember, this can be an individual FormControl or a FormGroup
 	meta: StringMap;
 	propagateChange?: Function;
 	call?: EventEmitter<string>;
 }
-type FFCCustom = FFC & ControlValueAccessor;
+type FFCCustom = IFFC & ControlValueAccessor;
 
 // Generate component name given type
 const componentType = (type: string): string => type[0].toUpperCase() + type.slice(1) + 'Component';
@@ -48,7 +48,7 @@ export class DynafieldDirective extends NgControl implements OnInit, OnDestroy {
 	@Output()
 	call: EventEmitter<string> = new EventEmitter<string>();
 
-	component: ComponentRef<FFC|FFCCustom>;
+	component: ComponentRef<IFFC|FFCCustom>;
 	_control;
 
 	constructor(
@@ -72,10 +72,10 @@ export class DynafieldDirective extends NgControl implements OnInit, OnDestroy {
 		}
 		try {
 			let { control, meta } = this;
-			const { name, class: cssClass, id: cssId, isDisabled } = meta;
+			const { name, class: cssClass, id: cssId, disabled } = meta;
 
 			// Create the component
-			const component = this.resolver.resolveComponentFactory<FFC>(formFieldComponents[type]);
+			const component = this.resolver.resolveComponentFactory<IFFC>(formFieldComponents[type]);
 			this.component = this.container.createComponent(component);
 			const instance = this.component.instance;
 			const el = this.component.location.nativeElement;
@@ -90,7 +90,7 @@ export class DynafieldDirective extends NgControl implements OnInit, OnDestroy {
 			}
 
 			// Check whether it's disabled, then set its FormControl and metadata
-			if (isDisabled) {
+			if (disabled) {
 				this.control.reset({ value: this.control.value, disabled: true });
 			}
 			instance.control = control;
@@ -166,10 +166,10 @@ export class DynafieldDirective extends NgControl implements OnInit, OnDestroy {
 	}
 
 	normalizeValidator(validator: ValidatorFn | Validator): ValidatorFn {
-		if ((<Validator>validator).validate) {
-			return (c: AbstractControl) => (<Validator>validator).validate(c);
+		if ((validator as Validator).validate) {
+			return (c: AbstractControl) => (validator as Validator).validate(c);
 		} else {
-			return <ValidatorFn>validator;
+			return validator as ValidatorFn;
 		}
 	}
 

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

@@ -170,10 +170,12 @@ export class DynaformComponent implements OnInit {
 	}
 
 	private displayDebuggingInConsole(): void {
-		console.log('%c *** MetaData *** ', this.conGreen);
-		console.dir(this.formMetaData);
-		console.log('%c *** FormGroup *** ', this.conGreen);
-		console.dir(this.formGroup);
+		if (this.debug) {
+			console.log('%c *** MetaData *** ', this.conGreen);
+			console.dir(this.formMetaData);
+			console.log('%c *** FormGroup *** ', this.conGreen);
+			console.dir(this.formGroup);
+		}
 	}
 
 }

+ 32 - 20
src/app/dynaform/models/field.model.ts

@@ -9,7 +9,7 @@ import { ValidatorFn, AsyncValidatorFn } from '@angular/forms';
 import { ValueTransformer } from './../interfaces';
 import { standardModifiers, standardTransformer } from './../utils';
 
-interface SimpleFieldMetaData {
+interface ISimpleFieldMetaData {
 	name: string; 							// The FormControl name
 	source?: string;						// Location in API-returned model - defaults to name
 	type?: string; 							// The component type e.g. BasicInput, Checkbutton, Timepicker, etc
@@ -21,35 +21,35 @@ interface SimpleFieldMetaData {
 	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>
-	isDisabled?: 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
 	onChange?: (val) => {};					// Function to call when field's value changes
 }
 
-interface Option {
+interface IOption {
 	label: string;
 	value: string | number | boolean;
 }
 
-interface OptionsFieldMetaData extends SimpleFieldMetaData {
+interface IOptionsFieldMetaData extends ISimpleFieldMetaData {
 	options;									// Array of Options - for select, radio-button-group and other 'multiple-choice' types
 	horizontal?: boolean;						// Whether to arrang radio buttons or checkboxes horizontally (default false)
 }
 
 // For components that include links to other pages
-interface Link {
+interface ILink {
 	label: string;
 	route: any[] | string;
 }
 
-interface DropdownModifiedInputFieldMetaData extends SimpleFieldMetaData {
+interface IDropdownModifiedInputFieldMetaData extends ISimpleFieldMetaData {
 	modifiers: string[];
 	transform: ValueTransformer;
 }
 
-interface TimePickerFieldMetaData extends SimpleFieldMetaData {
+interface ITimePickerFieldMetaData extends ISimpleFieldMetaData {
 	value: Date | string;
 	format: string;
 	steps: StringMap;
@@ -78,12 +78,12 @@ abstract class SimpleField {
 	placeholder = '';
 	class?: string | string[];
 	id?: string;
-	isDisabled = false;
+	disabled = false;
 	validators: ValidatorFn[] = [];
 	asyncValidators: AsyncValidatorFn[] = [];
 	valFailureMsgs: StringMap = {};
 
-	constructor(meta: SimpleFieldMetaData) {
+	constructor(meta: ISimpleFieldMetaData) {
 		if (meta.type === 'Multiline') {
 			console.log(meta);
 		}
@@ -100,9 +100,11 @@ abstract class SimpleField {
 	}
 }
 
-class Option {
+class Option implements IOption {
 	// Can take a simple string value, a value-label pair [value, label],
 	// or an Option of the form { label: string, value: string }
+	label: string;
+	value: string | number | boolean;
 	constructor(opt: string | string[] | Option) {
 		if (typeof opt === 'object') {
 			if (Array.isArray(opt)) {
@@ -121,7 +123,7 @@ class Option {
 
 abstract class OptionsField extends SimpleField {
 	options: Option[] = [];
-	constructor(meta: OptionsFieldMetaData) {
+	constructor(meta: IOptionsFieldMetaData) {
 		super(meta);
 		if (Array.isArray(meta.options)) {
 			this.options = meta.options.reduce((acc, opt) => { acc.push(new Option(opt)); return acc; }, []);
@@ -139,7 +141,7 @@ abstract class OptionsField extends SimpleField {
 
 class TextField extends SimpleField {
 	type = 'Text';
-	link?: Link;
+	link?: ILink;
 }
 
 class TextareaField extends SimpleField {
@@ -152,7 +154,7 @@ class PasswordField extends SimpleField {
 
 class SelectField extends OptionsField {
 	type = 'Select';
-	link?: Link;
+	link?: ILink;
 }
 
 class RadioField extends OptionsField {
@@ -178,7 +180,7 @@ class DropdownModifiedInputField extends SimpleField {
 	type = 'DropdownModifiedInput';
 	modifiers: string[] = standardModifiers;
 	transform: ValueTransformer = standardTransformer;
-	constructor(meta: DropdownModifiedInputFieldMetaData) {
+	constructor(meta: IDropdownModifiedInputFieldMetaData) {
 		super(meta);
 	}
 }
@@ -214,7 +216,7 @@ class CheckbuttonGroup {
 		} else {
 			const groupMembers = groupmeta.meta;
 			this.meta = Object.entries(groupMembers)
-				.map( ([key, cb]) => [key, new CheckbuttonField(cb as SimpleFieldMetaData)] )
+				.map( ([key, cb]) => [key, new CheckbuttonField(cb as ISimpleFieldMetaData)] )
 				.reduce((res, [key, cbf]) => { res[key as string] = cbf; return res; }, {});
 		}
 	}
@@ -257,7 +259,6 @@ class Container {
 	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);
@@ -268,14 +269,14 @@ class Container {
 // ---------------------------------------------------------------------------------------------------------------------
 // Button Group
 
-interface ButtonInterface {
+interface IButtonInterface {
 	label: string;
 	fnId: string;
 	class?: string;
 	icon?: string;
 }
 
-class Button implements ButtonInterface {
+class Button implements IButtonInterface {
 	label;
 	fnId;
 	class = 'btn-primary';
@@ -310,6 +311,18 @@ class Heading {
 	}
 }
 
+// ---------------------------------------------------------------------------------------------------------------------
+// Display
+
+class DisplayField {
+	value: string;
+	link?: ILink;
+	readonly noFormControls = true; // Indicates this has no FormControls associated with it
+	constructor(meta) {
+		Object.assign(this, meta);
+	}
+}
+
 // ---------------------------------------------------------------------------------------------------------------------
 // ---------------------------------------------------------------------------------------------------------------------
 // ---------------------------------------------------------------------------------------------------------------------
@@ -321,6 +334,5 @@ export {
 	CheckbuttonField, DropdownModifiedInputField, MultilineField,
 	CheckbuttonGroup,
 	TimepickerField, DatepickerField,
-	Container, ButtonGroup, Heading
+	Container, ButtonGroup, Heading, DisplayField
 };
-

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

@@ -20,21 +20,23 @@
  *
  */
 
-import { FormBuilder, FormGroup, FormArray, FormControl, ValidatorFn, AsyncValidatorFn } from '@angular/forms';
+import { FormBuilder, FormGroup, FormArray, FormControl, AbstractControlOptions } from '@angular/forms';
 import { reduce, cloneDeep } from 'lodash/fp';
 import * as fmdModels from '../models/field.model';
 
 // REMINDER: Import this directly from @angular/forms when we upgrade to Angular 6
 // While we're still on Angular 5 just declare it
+/*
 interface AbstractControlOptions {
 	validators?: ValidatorFn | ValidatorFn[] | null;
 	asyncValidators?: AsyncValidatorFn | AsyncValidatorFn[] | null;
 	updateOn?: 'change' | 'blur' | 'submit';
 }
+*/
 
 
 // ---------------------------------------------------------------------------------------------------------------------
-// AutoMeta: Generate Automatic Metadata from a raw model (or mapped raw model)
+// AutoMeta: Generate Automatic Metadata from a model
 // ---------------------------------------------------------------------------------------------------------------------
 
 const isScalar = val => typeof val === 'boolean' || typeof val === 'number' || typeof val === 'string';
@@ -64,11 +66,11 @@ const autoMeta = model => Object.entries(model)
 
 // containerSeed = Metadata from container which seeds all contained fields
 
-const combineMetaForField = (metaF, containerSeed, extraMetaF) => Object.assign(metaF, containerSeed, extraMetaF);
+const combineMetaForField = (metaF, containerSeed, extraMetaF) => ({ ...metaF, ...containerSeed, ...extraMetaF });
 const combineExtraMeta = (metaG, extraMeta, createFromExtra = false, containerSeed = {}) => {
 	const combinedMeta = {};
 	Object.entries(extraMeta).forEach(([key, val]) => {
-		if (typeof metaG[key] === 'object' || createFromExtra) {
+		if (typeof metaG[key] === 'object' || createFromExtra) { // If the key exists (in the model) OR we're creating from metadata
 			const isCon = isContainer(val);
 			const metaFoG = metaG[key] || {};
 			const seed = isCon ? {} : containerSeed; // Container's don't seed themselves, only their children
@@ -125,7 +127,7 @@ const buildModeledFieldGroupMember = metaFoG => {
 };
 
 // Build Form Group
-const buildModeledFieldGroupReducerIteree = (res, metaFoG) => Object.assign(res, { [metaFoG.name]: buildModeledFieldGroupMember(metaFoG) });
+const buildModeledFieldGroupReducerIteree = (res, metaFoG) => ({ ...res, [metaFoG.name]: buildModeledFieldGroupMember(metaFoG) });
 const _buildFieldSpecificMeta = metaG => reduce(buildModeledFieldGroupReducerIteree, {}, metaG);
 const buildFieldSpecificMeta = metaG => _buildFieldSpecificMeta(addMissingNames(metaG));
 
@@ -265,7 +267,7 @@ const buildFormGroupFunctionFactory = (fb: FormBuilder): (meta) => FormGroup =>
 	// ( it's done this way so we can use the FormBuilder singleton without reinitialising )
 
 	// Build Form Control
-	const buildControlState = metaF => ({ value: metaF.value || metaF.default, disabled: metaF.isDisabled });
+	const buildControlState = metaF => ({ value: metaF.value || metaF.default, disabled: metaF.disabled });
 	const buildValidators = (metaF): AbstractControlOptions => ({
 		validators: metaF.validators,
 		asyncValidators: metaF.asyncValidators,
@@ -283,7 +285,7 @@ const buildFormGroupFunctionFactory = (fb: FormBuilder): (meta) => FormGroup =>
 		buildFormControl(metaFoG);
 
 	const buildFormGroupReducerIteree = (res, metaFoG) => {
-		return metaFoG.noFormControls ? res : Object.assign(res, { [metaFoG.name]: buildFormGroupMember(metaFoG) });
+		return metaFoG.noFormControls ? res : { ...res, [metaFoG.name]: buildFormGroupMember(metaFoG) };
 	};
 	const _buildFormGroup = _metaG => fb.group(reduce(buildFormGroupReducerIteree, {}, _metaG));
 

+ 15 - 11
src/app/dynaform/services/dynaform.service.ts

@@ -94,12 +94,12 @@ import {
 	buildFieldSpecificMeta, extractFieldMappings, buildFormGroupFunctionFactory, generateNewModel
 } from './_formdata-utils';
 
-export interface FormAndMeta {
+export interface IFormAndMeta {
 	form: FormGroup;
 	meta: StringMap;
 }
 
-export interface Callbacks {
+export interface ICallbacks {
 	[index: string]: () => void;
 }
 
@@ -108,7 +108,7 @@ export class DynaformService {
 
 	public buildFormGroup: (meta) => FormGroup;
 	private buildStrategy: 'MODELFIRST' | 'METAFIRST' = 'MODELFIRST'; // Make ENUM type
-	private callbacks: Callbacks = {};
+	private callbacks: ICallbacks = {};
 
 	private conGreen = 'color: white; background-color: green; font-weight: bold;';
 
@@ -127,12 +127,12 @@ export class DynaformService {
 		}
 	}
 
-	build(model: StringMap, meta = {}, createFromMeta = false): FormAndMeta {
+	build(model: StringMap, meta = {}, createFromMeta = false): IFormAndMeta {
 		// Short name for autoBuildFormGroupAndMeta
 		return this.autoBuildFormGroupAndMeta(model, meta, createFromMeta);
 	}
 
-	register(callbacks: Callbacks, cref: ComponentRef<any>['instance']) {
+	register(callbacks: ICallbacks, cref: ComponentRef<any>['instance']) {
 		if (cref) {
 			// Bind the component instance to the callback, so that 'this' has the context of the component
 			Object.entries(callbacks).forEach(([key, fn]) => this.callbacks[key] = fn.bind(cref));
@@ -142,12 +142,16 @@ export class DynaformService {
 	}
 
 	call(fnId: string) {
-		// Handle callback events
-		try {
-			this.callbacks[fnId]();
-		} catch (e) {
+		// Handle callbacks
+		if (typeof this.callbacks[fnId] === 'function') {
+			try {
+				this.callbacks[fnId]();
+			} catch (e) {
+				console.error('Error thrown during callback', fnId);
+				console.error(e);
+			}
+		} else {
 			console.error('Dynaform has no registered callback for', fnId);
-			console.error(e);
 		}
 	}
 
@@ -164,7 +168,7 @@ export class DynaformService {
 	// -----------------------------------------------------------------------------------------------------------------
 	// Convenience methods combining several steps
 
-	autoBuildFormGroupAndMeta(model: StringMap, meta = {}, createFromMeta = false): FormAndMeta {
+	autoBuildFormGroupAndMeta(model: StringMap, meta = {}, createFromMeta = false): IFormAndMeta {
 		let _model;
 		if (this.buildStrategy === 'MODELFIRST') {
 			_model = model;

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

@@ -147,7 +147,7 @@ export class ModelMapperService {
 							this.clog('-------------------------------------------');
 							this.clog('CONTAINER WITH STORED FUNCTIONAL MAPPING', absPath);
 							try {
-								const storedContainerMapping = _mapping.__
+								const storedContainerMapping = _mapping.__;
 								let targetPath = typeof storedContainerMapping[0] === 'string' ? storedContainerMapping[0] : absPath;
 								if (targetPath[0] === '/') {
 									targetPath = targetPath.slice(1);
@@ -176,7 +176,7 @@ export class ModelMapperService {
 						}
 						break;
 					default:
-						this.deepSet(res, key, `MAPPING ERROR: Unknown mapping type in mapping at ${key}`); 
+						this.deepSet(res, key, `MAPPING ERROR: Unknown mapping type in mapping at ${key}`);
 				}
 			}
 		});
@@ -201,7 +201,7 @@ export class ModelMapperService {
 		}
 		const res = {};
 		const modelClone = stack.length ? model : _.cloneDeep(model); // Clone the model unless inside a recursive call
-		Object.keys(mapping).filter(key => key !== '__').forEach(key => { 
+		Object.keys(mapping).filter(key => key !== '__').forEach(key => {
 			const dataMapping = mapping[key];
 			const mappingType = this.resolveMappingType(dataMapping);
 			switch(mappingType) {
@@ -281,6 +281,7 @@ export class ModelMapperService {
 		if (t === 'number' || t === 'string') { // CHECK WHAT HAPPENS with number types
 			mappingType = 'simple';
 		} else if (t === 'object') {
+			// tslint:disable-next-line:prefer-conditional-expression
 			if (
 				Array.isArray(mappingPath)
 				&& mappingPath.length === 2
@@ -343,7 +344,7 @@ export class ModelMapperService {
 			}
 			_.set(obj, mappingPath, merged);
 		} else {
-			this.errors.push('WARNING: Could not merge', typeof valueToSet, 'with object')
+			this.errors.push('WARNING: Could not merge', typeof valueToSet, 'with object');
 		}
 	}
 
@@ -355,7 +356,7 @@ export class ModelMapperService {
 			.reduce((newObj, k) => {
 				typeof obj[k] === 'object' ?
 					Object.assign(newObj, { [k]: this.deepCleanse(obj[k]) }) : // Recurse if value is non-empty object
-					Object.assign(newObj, { [k]: obj[k] }) // Copy value
+					Object.assign(newObj, { [k]: obj[k] }); // Copy value
 				return newObj;
 			}, {});
 		Object.keys(cleansedObj).forEach(k => {

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

@@ -41,7 +41,7 @@ const radioField = {
 const disabledTextField = {
 	type: 'Text',
 	placeholder: 'You can\'t touch this',
-	isDisabled: true
+	disabled: true
 };
 
 const radioFieldHorizontal = {

+ 8 - 8
src/app/dynaform/testdata/testset.4.ts

@@ -2,12 +2,12 @@
 // test of special source characters
 
 const dumbFlatten = obj => {
-	return Object.assign( {}, ...function _flatten(objectBit) {  
-		return [].concat(                                                       
-			...Object.keys(objectBit).map(                                      
-				key => typeof objectBit[key] === 'object' ? _flatten(objectBit[key]) : { [key]: objectBit[key] }              
+	return Object.assign( {}, ...function _flatten(objectBit) {
+		return [].concat(
+			...Object.keys(objectBit).map(
+				key => typeof objectBit[key] === 'object' ? _flatten(objectBit[key]) : { [key]: objectBit[key] }
 			)
-		)
+		);
 	}(obj));
 };
 
@@ -36,7 +36,7 @@ const testReturnRoot = obj => ({
 const testReturnFH = obj => ({
 	i: obj.i
 });
-  
+
 const model = {
 	a: 1,
 	b: 2,
@@ -47,7 +47,7 @@ const model = {
 		g: 6,
 		h: {
 			i: 7,
-			j: 8, 
+			j: 8,
 			k: {
 				l: {
 					m: 'Mmmmm...'
@@ -118,7 +118,7 @@ const meta = {
 			}
 		}
 	}
-}
+};
 
 export { model, meta };
 

+ 0 - 13
src/ng-dynaform.code-workspace

@@ -1,13 +0,0 @@
-{
-	"folders": [
-		{
-			"path": "app"
-		},
-		{
-			"path": "app/dynaform"
-		},
-		{
-			"path": "/Users/rk/play/ng-dynaform"
-		}
-	]
-}