ソースを参照

Syncing with AMP prior to AOT refactor

Richard Knight 6 年 前
コミット
68b29b10a1

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

@@ -1,3 +1,4 @@
 <clr-password-container>
+	<label>{{ label }}</label>
     <input clrPassword [formControl]="control" [placeholder]="placeholder" />
 </clr-password-container>

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

@@ -8,6 +8,6 @@ import { NativeInputComponent } from '../../_abstract/native-input.component';
 })
 export class ClrPasswordComponent extends NativeInputComponent {
 
-	exposeMetaInTemplate: string[] = ['placeholder'];
+	exposeMetaInTemplate: string[] = ['label', 'placeholder'];
 
 }

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

@@ -56,6 +56,6 @@ export class CheckbuttonComponent extends CustomInputComponent implements OnChan
 		value = value ? this.checkedValue : false;
 		this.isChecked = !!value;
 		this.currentValue = this.isChecked ? this.checkedValue : false;
-		this._cdr.markForCheck(); // We have to manually trigger change detection when using setVAlue or patchValue from outside this component
+		this._cdr.markForCheck(); // We have to manually trigger change detection when using setValue or patchValue from outside this component
 	}
 }

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

@@ -1 +1 @@
-<h3 class="clr-col-sm-12 h-dyna" [ngClass]="'h-dyna-' + (level)">{{ text }}</h3>
+<h3 class="h-dyna" [ngClass]="'h-dyna-' + (level)">{{ text }}</h3>

+ 15 - 5
src/app/dynaform/dynaform.component.html

@@ -33,17 +33,26 @@
 						</a>
 						<a *ngIf="meta.showAddControl"
 							class="btn btn-sm btn-outline-success"
+							[ngClass]="{ 'btn-disabled': !addRCMemberAllowed(meta.name) }"
 							(click)="addRCMember(meta.name)">
 							+ Add New
 						</a>
 					</div>
 				</div>
 				<div *ngFor="let container of meta.meta; let i = index" class="dyna-rc-container" [ngClass]="{ 'dyna-rc-display-all': meta.display === 'ALL' }">
+					<div *ngIf="meta.showDeleteControl" class="text-right">
+						<a	[ngClass]="{ 'btn-disabled': !deleteRCMemberAllowed(meta.name) }"
+							(click)="deleteRCMember(meta.name, i)">
+							<i class="fa fa-trash"></i>
+						</a>
+					</div>
 					<ng-container *ngTemplateOutlet="dynaform; context: getRCTemplateContext(meta.name, i)"></ng-container>
 				</div>
 				<div *ngIf="meta.showAddControl && meta.display === 'ALL'" class="clr-row">
 					<div class="clr-col-sm-12">
-						<a class="btn btn-sm btn-outline-success" (click)="addRCMember(meta.name)">
+						<a class="btn btn-sm btn-outline-success"
+							[ngClass]="{ 'btn-disabled': !addRCMemberAllowed(meta.name) }"
+							(click)="addRCMember(meta.name)">
 							+ Add New
 						</a>
 					</div>
@@ -58,14 +67,16 @@
 		<ng-template #fullWidth>
 			<ng-container dynafield [control]="control" [meta]="meta" (call)="handleCallback($event)"></ng-container>
 		</ng-template>
-		
+
 	</ng-container>
 </ng-template>
 
 
 <ng-template let-control="control" let-meta="meta" #dynaform>
-	<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>
+	<div [ngClass]="getRowClass(control, meta)">
+		<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>
+	</div>
 </ng-template>
 
 
@@ -81,4 +92,3 @@
 			<pre class="alert-text" style="border: none;">{{ formGroup.pristine ? 'Untouched' : getValidationErrors() | json }}</pre>
 		</div>
 </div>
-	

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

@@ -1,7 +1,8 @@
 import { Component, Input, Output, EventEmitter, TemplateRef, Optional, OnInit, ChangeDetectionStrategy } from '@angular/core';
-import { FormControl, FormGroup, FormArray, FormGroupName, AbstractControl, ControlContainer } from '@angular/forms';
+import { FormBuilder, FormControl, FormGroup, FormArray, FormGroupName, AbstractControl, ControlContainer } from '@angular/forms';
 import { SuperForm } from 'angular-super-validator';
-import { unwrapResolvedMetadata } from '@angular/compiler';
+import { buildFormGroupFunctionFactory } from './services/_formdata-utils';
+import { cloneDeep } from 'lodash/fp';
 
 export interface DynarowContext {
 	control: AbstractControl;
@@ -12,7 +13,7 @@ export interface DynarowContext {
 	selector: 'app-dynaform',
 	templateUrl: './dynaform.component.html',
 	styleUrls: ['./dynaform.component.scss'],
-	changeDetection: ChangeDetectionStrategy.OnPush
+	changeDetection: ChangeDetectionStrategy.Default // or ChangeDetectionStrategy.OnPush - might be more efficient. Experiment later.
 })
 export class DynaformComponent implements OnInit {
 
@@ -151,7 +152,7 @@ export class DynaformComponent implements OnInit {
 	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}` : '';
+		const containerClass = fieldClass ? (meta.type === 'Container' ? ` ${fieldClass}` : ` row-${fieldClass}`) : '';
 		const errorClass = control && control.touched && control.invalid ? ' dyna-error' : '';
 		return `row-${fieldTypeClass}${containerClass}${errorClass}`;
 	}
@@ -180,14 +181,48 @@ export class DynaformComponent implements OnInit {
 		
 	}
 
+	// RC = Repeating Container
+	addRCMemberAllowed(repeatingContainerName: string): boolean {
+		const rcMeta = this.formMetaData[repeatingContainerName];
+		return typeof rcMeta.maxRepeat === 'number' && rcMeta.maxRepeat > rcMeta.meta.length;
+	}
+
+	// Maybe move the AddRC and deleteRC funtions to _formdata-utils.ts ?
 	addRCMember(repeatingContainerName: string): void {
+		// (1) Check that we can still add controls
+		if (!this.addRCMemberAllowed(repeatingContainerName)) {
+			return;
+		}
+		// (2) Add metadata for new container member
 		const rcMeta = this.formMetaData[repeatingContainerName];
-		rcMeta.meta.push(rcMeta.__defaultContainer);
-		console.log(rcMeta);
+		const containerTemplate = cloneDeep(rcMeta.__containerTemplate);
+		const i = this.formMetaData[repeatingContainerName].meta.length;
+		containerTemplate.name = `group${i+1}`;
+		rcMeta.meta.push(containerTemplate);
+		// (3) Add FormGroup for new container member
+		const buildFormGroup = buildFormGroupFunctionFactory(new FormBuilder());
+		const newFormGroup = buildFormGroup(containerTemplate.meta);
+		(this.formGroup.get(repeatingContainerName) as FormArray).push(newFormGroup);
 	}
 
-	deleteRCMember(repeatingContainerName: string, index: number): void {
+	deleteRCMemberAllowed(repeatingContainerName: string): boolean {
+		const rcMeta = this.formMetaData[repeatingContainerName];
+		return typeof rcMeta.minRepeat === 'number' && rcMeta.minRepeat < rcMeta.meta.length;
+	}
 
+	deleteRCMember(repeatingContainerName: string, index: number): void {
+		// (1) Check that we can still delete controls
+		if (!this.deleteRCMemberAllowed(repeatingContainerName)) {
+			return;
+		}
+		// (2) Delete from the metadata, and rename the groups
+		const rcMeta = this.formMetaData[repeatingContainerName];
+		const metaArr = rcMeta.meta;
+		const newMetaArr = [ ...metaArr.slice(0, index), ...metaArr.slice(index + 1) ]
+			.map((m, i) => { m.name = `group${i+1}`; return m; });
+		rcMeta.meta = newMetaArr;
+		// (3) Delete the corresponding FormGroup from the FormArray
+		(this.formGroup.get(repeatingContainerName) as FormArray).removeAt(index);
 	}
 
 	getValidationFailureMessage(control: FormControl, meta: StringMap<any>) {

+ 4 - 3
src/app/dynaform/models/field.model.ts

@@ -250,7 +250,6 @@ class CheckbuttonGroup extends CheckboxGroup {
 	type = 'CheckbuttonGroup';
 }
 
-
 // ---------------------------------------------------------------------------------------------------------------------
 // Concrete Implementations - Kendo Form Components
 
@@ -286,8 +285,10 @@ class Container {
 	label = '';
 	seed: StringMap<any>;
 	template?: TemplateRef<any>;
-	button: string;	
-	focussed: boolean = true;	
+	button: string;						// IS THIS ACTUALLY USED?
+	focussed: boolean = true;			// IS THIS ACTUALLY USED?
+	class?: string | string[];
+	id?: string;
 	meta: StringMap<any>; // TODO: Tighten up on type with union type
 	constructor(containerMeta: StringMap<any>) {
 		Object.assign(this, containerMeta);

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

@@ -95,7 +95,7 @@ const combineExtraMeta = (metaG, extraMeta, createFromExtra = false, containerSe
 				combinedMeta[key] = combineMetaForField(metaFoG, {}, extra);
 				
 				// Stash a 'conbtainer template' for adding extra containers to the repeating container
-				combinedMeta[key].__defaultContainer = combineExtraMeta(
+				combinedMeta[key].__containerTemplate = combineExtraMeta(
 					cloneDeep(baseObjWithAllKeys),
 					val['meta'][0],
 					false,
@@ -204,10 +204,10 @@ const buildFieldSpecificMetaInClosure = (metaG, context) => {
 			modeledGroupMember.meta = _buildFieldSpecificMeta(modeledGroupMember.meta);
 		} else if (isRepeatingContainer(metaFoG)) {
 			modeledGroupMember.meta = modeledGroupMember.meta.map(rcMem => ({ ...rcMem, meta: _buildFieldSpecificMeta(rcMem.meta) }));
-			modeledGroupMember.__defaultContainer = {
+			modeledGroupMember.__containerTemplate = {
 				...modeledGroupMember.meta[0],
-				meta: _buildFieldSpecificMeta(modeledGroupMember.__defaultContainer),
-				name: '__defaultContainer',
+				meta: _buildFieldSpecificMeta(modeledGroupMember.__containerTemplate),
+				name: '__containerTemplate',
 				button: ''
 			};
 		}
@@ -457,13 +457,15 @@ const execMetaReorderingInstructions = (metaG: StringMap<any>) => {
 // (used to produce an updated copy of a model when form values are changed - will not create new keys)
 // ---------------------------------------------------------------------------------------------------------------------
 
+const isRealObject = val => val !== null && typeof val === 'object'; // js quirk - typeof null ---> 'object'
+
 const generateNewModel = (originalModel, updates) => {
 	return updateObject(originalModel, updates);
 };
 
 const updateObject = (obj, updates, createAdditionalKeys = false) => {
 	// THIS DOES NOT MUTATE obj, instead returning a new object
-	if (typeof obj !== 'object') {
+	if (!isRealObject(obj)) {
 		obj = {};
 	}
 	console.log('obj is', obj, typeof obj);

+ 2 - 2
src/app/dynaform/services/dynaform.service.ts

@@ -117,7 +117,7 @@ export class DynaformService {
 	private context: any;
 
 	public buildFormGroup: (meta) => FormGroup;
-	private buildStrategy: 'MODELFIRST' | 'METAFIRST' = 'MODELFIRST'; // Make ENUM type
+	private buildStrategy: 'MODELFIRST' | 'METAFIRST' = 'METAFIRST'; // Make ENUM type
 	private callbacks: ICallbacks = {};
 
 	private conGreen = 'color: white; background-color: green; font-weight: bold;';
@@ -129,7 +129,7 @@ export class DynaformService {
 	setBuildStrategy(str): void {
 		switch (str.toUpperCase()) {
 			case 'MODELFIRST':	// Build from model, combining optional extra metadata
-			case 'METAFIRST':	// Build from metadata, sourcing values from the model (usindg meta's 'source' attribute)
+			case 'METAFIRST':	// Build from metadata, sourcing values from the model (optionally, using meta's 'source' attribute)
 				this.buildStrategy = str.toUpperCase();
 				break;
 			default: