Skip to content

Commit

Permalink
feat(dynamic-forms): allow elements to be disabled via configuration (#…
Browse files Browse the repository at this point in the history
…1219)

* refactor(dynamic-forms): remove ngModel usage

this is needed because we were mixing reactive forms with template driven forms
and this usage will be deprecated in angular 7

also used the covalent control value accessor mixin

* chore(): remove AbstractControlValueAccessor code since its not needed

* fix(): form support was not needed in the dynamic elements

since we are injecting the control instance into the underlying element
form support was never needed

* feat(dynamic-forms): allow elements to be disabled via configuration

adding a disabled property to disable properties on the fly without the
need of accessing the underlying form

* fix(): disable input in dynamic file input

* fix(): fix label scss for slider
  • Loading branch information
emoralesb05 authored Aug 15, 2018
1 parent 95c773d commit 2e2b8a0
Show file tree
Hide file tree
Showing 11 changed files with 104 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,28 @@
<h4>Form elements</h4>
<ng-template let-model let-last="last" let-index="index" ngFor [ngForOf]="dynamicElements">
<td-expansion-panel [label]="model.name">
<mat-divider></mat-divider>
<mat-divider [inset]="true"></mat-divider>
<div class="pad" layout="column">
<div layout="row">
<mat-form-field *ngIf="!isMinMaxSupported(model.type)"class="pad-right-xs" flex>
<input matInput
placeholder="Default"
[(ngModel)]="model.default"
name="default">
</mat-form-field>
<mat-form-field *ngIf="!isDate(model.type) && isMinMaxSupported(model.type)" class="pad-right-xs" flex>
<input matInput type="number" placeholder="Default" [(ngModel)]="model.default" name="default">
</mat-form-field>
<mat-form-field *ngIf="isDate(model.type) && isMinMaxSupported(model.type)" class="pad-right-xs" flex>
<input matInput
[matDatepicker]="defaultDatepicker"
placeholder="Default"
[(ngModel)]="model.default"
name="default">
<mat-datepicker-toggle matSuffix [for]="defaultDatepicker"></mat-datepicker-toggle>
<mat-datepicker #defaultDatepicker></mat-datepicker>
</mat-form-field>
</div>
<div layout="row">
<mat-form-field class="pad-right-xs" flex>
<input matInput
Expand Down Expand Up @@ -134,6 +154,7 @@ <h4>Form elements</h4>
</div>
<div layout="row">
<mat-slide-toggle [(ngModel)]="model.required" name="required">Required</mat-slide-toggle>
<mat-slide-toggle [(ngModel)]="model.disabled" name="disabled">Disabled</mat-slide-toggle>
<span flex></span>
<button mat-icon-button matTooltip="Delete element" (click)="deleteElement(index)">
<mat-icon>delete</mat-icon>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export class DynamicFormsDemoComponent {
required: false,
default: 'Default',
flex: 50,
disabled: true,
}, {
name: 'textarea',
hint: 'this is a textarea hint',
Expand Down
2 changes: 2 additions & 0 deletions src/platform/dynamic-forms/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export interface ITdDynamicElementConfig {
hint?: string;
type: TdDynamicType | TdDynamicElement | Type<any>;
required?: boolean;
disabled?: boolean;
min?: any;
max?: any;
minLength?: any;
Expand Down Expand Up @@ -139,6 +140,7 @@ export class Demo {
name: 'boolean',
type: TdDynamicType.Boolean,
default: false,
disabled: true,
}, {
name: 'select',
type: TdDynamicElement.Select,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@
<mat-form-field tdFileDrop
class="td-dynamic-file-input-field"
floatLabel="never"
[disabled]="control?.disabled"
(fileDrop)="_handlefileDrop($event)"
(click)="fileInput.inputElement.click()"
(keyup.enter)="fileInput.inputElement.click()"
(click)="!control?.disabled && fileInput.inputElement.click()"
(keyup.enter)="!control?.disabled && fileInput.inputElement.click()"
(keyup.delete)="fileInput.clear()"
(keyup.backspace)="fileInput.clear()">
<input matInput
[value]="control?.value?.name"
[placeholder]="label"
[disabled]="control?.disabled"
readonly/>
<mat-hint>{{hint}}</mat-hint>
<mat-error>
Expand All @@ -22,7 +24,9 @@
<button mat-icon-button *ngIf="control.value" (click)="fileInput.clear()" (keyup.enter)="fileInput.clear()">
<mat-icon>cancel</mat-icon>
</button>
<td-file-input class="td-file-input" #fileInput [formControl]="control">
<td-file-input class="td-file-input"
#fileInput
[formControl]="control">
<mat-icon>folder</mat-icon>
<span>{{ label }}</span>
</td-file-input>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
<div class="td-dynamic-slider-wrapper">
<div class="mat-form-field-placeholder-wrapper mat-form-field-can-float mat-form-field-should-float"
[class.mat-focused]="slider._isActive">
<label class="mat-form-field-placeholder mat-float mat-form-field-float td-slider-label"> {{label}} <span *ngIf="required" class="mat-placeholder-required">*</span></label>
</div>
<div class="td-dynamic-slider-wrapper mat-form-field mat-form-field-can-float mat-form-field-should-float"
[class.mat-focused]="slider._isActive">
<span class="mat-form-field-label-wrapper">
<label class="mat-form-field-label mat-primary td-slider-label">
{{label}}
<span *ngIf="required && !control?.disabled" class="mat-form-field-required-marker">*</span>
</label>
</span>
<div class="td-dynamic-slider-field">
<mat-slider #slider
class="td-dynamic-slider"
[formControl]="control"
class="td-dynamic-slider"
[formControl]="control"
[min]="min"
[max]="max"
thumbLabel
tickInterval="auto"
[required]="required">
[required]="required"
(blur)="_handleBlur()">
</mat-slider>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
:host {
.td-dynamic-slider-wrapper {
display: block;
}
}
.td-dynamic-slider-field {
position: relative;
margin-top: 8px;
// [layout="row"]
flex-direction: row;
// [layout]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component } from '@angular/core';
import { Component, ChangeDetectorRef } from '@angular/core';
import { FormControl } from '@angular/forms';

@Component({
Expand All @@ -18,4 +18,11 @@ export class TdDynamicSliderComponent {

max: number = undefined;

constructor(private _changeDetectorRef: ChangeDetectorRef) {}

_handleBlur(): void {
setTimeout(() => {
this._changeDetectorRef.markForCheck();
});
}
}
5 changes: 5 additions & 0 deletions src/platform/dynamic-forms/dynamic-forms.component.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
.td-dynamic-form-wrapper {
::ng-deep {
.mat-form-field-infix {
width: auto;
}
}
// [layout-wrap]
flex-wrap: wrap;
// [layout]
Expand Down
32 changes: 29 additions & 3 deletions src/platform/dynamic-forms/dynamic-forms.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ describe('Component: TdDynamicForms', () => {
fixture.debugElement.query(By.directive(TdDynamicFormsComponent)).componentInstance;
expect(dynamicFormsComponent.valid).toBeFalsy();
/* tslint:disable-next-line */
expect(JSON.stringify(dynamicFormsComponent.value)).toBe(JSON.stringify({first_name: null, on_it: true}));
expect(JSON.stringify(dynamicFormsComponent.value)).toBe(JSON.stringify({on_it: true}));
});
})));

Expand All @@ -127,7 +127,7 @@ describe('Component: TdDynamicForms', () => {
fixture.debugElement.query(By.directive(TdDynamicFormsComponent)).componentInstance;
expect(dynamicFormsComponent.valid).toBeFalsy();
/* tslint:disable-next-line */
expect(JSON.stringify(dynamicFormsComponent.value)).toBe(JSON.stringify({first_name: null, age: 17}));
expect(JSON.stringify(dynamicFormsComponent.value)).toBe(JSON.stringify({age: 17}));
});
})));

Expand Down Expand Up @@ -319,8 +319,34 @@ describe('Component: TdDynamicForms', () => {
});
})));

it('should render dynamic custom element', async(inject([], () => {
it('should render dynamic elements with one element disabled', async(inject([], () => {
let fixture: ComponentFixture<any> = TestBed.createComponent(TdDynamicFormsTestComponent);
let component: TdDynamicFormsTestComponent = fixture.debugElement.componentInstance;

expect(fixture.debugElement.queryAll(By.directive(TdDynamicElementComponent)).length).toBe(0);
component.elements = [{
name: 'hexColor',
type: TdDynamicType.Text,
required: true,
default: '#F1F1F1',
}, {
name: 'number',
type: TdDynamicType.Number,
disabled: true,
required: true,
}];
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(fixture.debugElement.queryAll(By.directive(TdDynamicElementComponent)).length).toBe(2);
let dynamicFormsComponent: TdDynamicFormsComponent =
fixture.debugElement.query(By.directive(TdDynamicFormsComponent)).componentInstance;
expect(dynamicFormsComponent.valid).toBeTruthy();
/* tslint:disable-next-line */
expect(JSON.stringify(dynamicFormsComponent.value)).toBe(JSON.stringify({hexColor: '#F1F1F1'}));
});
})));

it('should render dynamic custom element', async(inject([], () => {
let fixture: ComponentFixture<any> = TestBed.createComponent(TdDynamicFormsTestComponent);
let component: TdDynamicFormsTestComponent = fixture.debugElement.componentInstance;

Expand Down
13 changes: 11 additions & 2 deletions src/platform/dynamic-forms/dynamic-forms.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,19 @@ export class TdDynamicFormsComponent implements AfterContentInit {
throw new Error(`Dynamic element name: "${elem.name}" is duplicated`);
}
duplicates.push(elem.name);
if (!this.dynamicForm.get(elem.name)) {
let dynamicElement: AbstractControl = this.dynamicForm.get(elem.name);
if (!dynamicElement) {
this.dynamicForm.addControl(elem.name, this._dynamicFormsService.createFormControl(elem));
} else {
this.dynamicForm.get(elem.name).setValidators(this._dynamicFormsService.createValidators(elem));
dynamicElement.setValue(elem.default);
dynamicElement.markAsPristine();
dynamicElement.markAsUntouched();
if (elem.disabled) {
dynamicElement.disable();
} else {
dynamicElement.enable();
}
dynamicElement.setValidators(this._dynamicFormsService.createValidators(elem));
}
// copy objects so they are only changes when calling this method
this._renderedElements.push(Object.assign({}, elem));
Expand Down
3 changes: 2 additions & 1 deletion src/platform/dynamic-forms/services/dynamic-forms.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export interface ITdDynamicElementConfig {
hint?: string;
type: TdDynamicType | TdDynamicElement | Type<any>;
required?: boolean;
disabled?: boolean;
min?: any;
max?: any;
minLength?: any;
Expand Down Expand Up @@ -104,7 +105,7 @@ export class TdDynamicFormsService {
*/
createFormControl(config: ITdDynamicElementConfig): FormControl {
let validator: ValidatorFn = this.createValidators(config);
return new FormControl(config.default, validator);
return new FormControl({ value: config.default, disabled: config.disabled }, validator);
}

/**
Expand Down

0 comments on commit 2e2b8a0

Please sign in to comment.