Skip to content

Commit

Permalink
fix(dynamic-forms): dynamically add, edit, remove Form Controls (#787)
Browse files Browse the repository at this point in the history
* fix(dynamic-forms): #624 dynamically add, edit, remove Form Controls

At runtime, have the ability to dynamically add, edit, remove Form Controls

closes #624

* fix(dynamic-forms): #624 add validateDynamicElementName back

* chore(): simplify code by adding/removing controls and forcing a manual update

manual updates are great since checking if an array or its content has changes can be expensive in the long run, and most of the time not needed.. adding a refresh method gives the power to the developer to rerender the form if needed.

* chore(dynamic-forms): load README.md and add refresh method to its API

* feat(dynamic-forms): make flex width % configurable via element interface

* feat(dynamic-forms): support OnPush change detection

* feat(dynamic-forms): add demo to build your own form

* fix(dynamic-forms): add small fix for times where the control is created a bit later

* feat(dynamic-forms): add valid submit button to demo

* chore(dynamic-forms): add comments to code

* docs(dynamic-forms): new side-by-side pattern
  • Loading branch information
jerryorta-dev authored and emoralesb05 committed Aug 9, 2017
1 parent d88d0be commit e7be2a8
Show file tree
Hide file tree
Showing 8 changed files with 283 additions and 142 deletions.
203 changes: 116 additions & 87 deletions src/app/components/components/dynamic-forms/dynamic-forms.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,119 @@
<md-card-title>Dynamic Forms</md-card-title>
<md-card-subtitle>Build forms from a JS object</md-card-subtitle>
<md-divider></md-divider>
<md-toolbar hide show-gt-md>
<div layout="row" flex>
<span flex="50">Generated Form</span>
<span flex="50">Form Builder</span>
</div>
</md-toolbar>
<md-divider hide show-gt-md></md-divider>
<div layout-gt-md="row">
<div layout="column" class="bgc-grey-100 pad-sm" flex-gt-md="50">
<md-card flex>
<md-card-title>
<div layout="row" layout-align="start center" flex>
<div flex class="text-md">Toggle to reveal form JSON object</div>
<button md-icon-button mdTooltip="See JSON elements code" (click)="showDynamicCode = !showDynamicCode">
<md-icon>code</md-icon>
</button>
</div>
</md-card-title>
<md-divider></md-divider>
<div [tdToggle]="!showDynamicCode">
<td-highlight language="json" [content]="dynamicElements | json"></td-highlight>
</div>
<div class="pad">
<td-dynamic-forms #dynamicForm [elements]="dynamicElements">
</td-dynamic-forms>
</div>
<md-card-actions>
<button md-raised-button color="primary" class="text-upper" [disabled]="!dynamicForm.valid">
Submit
</button>
</md-card-actions>
</md-card>
</div>
<div class="pad" flex-gt-md="50">
<md-card-title class="push-top-sm push-bottom-sm">
<div layout="row" layout-align="start center" flex>
<div flex class="text-md">Select a type, add element then update</div>
</div>
</md-card-title>
<md-divider></md-divider>
<form #addForm="ngForm">
<div layout="row" layout-align="start center" class="pad-sm">
<md-select class="pad-right" placeholder="Select element type" floatPlaceholder="never" [(ngModel)]="type" name="type" required>
<md-option *ngFor="let option of elementOptions" [value]="option">
{{ option }}
</md-option>
</md-select>
<button mdTooltip="Add this element" md-mini-fab color="accent" [disabled]="!addForm.valid" (click)="addElement()">
<md-icon>add</md-icon>
</button>
</div>
</form>
<h4>Form elements</h4>
<ng-template let-model let-last="last" let-index="index" ngFor [ngForOf]="dynamicElements">
<td-expansion-panel [label]="model.name">
<md-divider></md-divider>
<div class="pad" layout="column">
<div layout="row">
<md-input-container class="pad-right-xs" flex>
<input mdInput
placeholder="Label"
[(ngModel)]="model.label"
name="label">
</md-input-container>
<md-input-container flex="30">
<input mdInput
type="number"
min="0"
max="100"
placeholder="Flex (width in %)"
[(ngModel)]="model.flex"
name="flex">
</md-input-container>
</div>
<div *ngIf="isMinMaxSupported(model.type)" layout="row">
<md-input-container class="pad-right-xs" flex>
<input mdInput
type="number"
placeholder="Min"
[(ngModel)]="model.min"
name="min">
</md-input-container>
<md-input-container flex="50">
<input mdInput
type="number"
placeholder="Max"
[(ngModel)]="model.max"
name="max">
</md-input-container>
</div>
<div layout="row">
<md-slide-toggle [(ngModel)]="model.required" name="required">Required</md-slide-toggle>
<span flex></span>
<button md-icon-button mdTooltip="Delete element" (click)="deleteElement(index)">
<md-icon>delete</md-icon>
</button>
</div>
</div>
</td-expansion-panel>
</ng-template>
<div class="push-top" layout="row">
<button md-raised-button color="primary" class="text-upper" (click)="dynamicForm.refresh()">
Update Form
</button>
</div>
</div>
</div>
</md-card>
<md-card>
<md-card-content>
<h3 class="md-title">Dynamic Text Elements</h3>
<md-divider></md-divider>
<md-tab-group md-stretch-tabs>
<md-tab-group md-stretch-tabs dynamicHeight>
<md-tab>
<ng-template md-tab-label>Demo</ng-template>
<td-dynamic-forms [elements]="textElements">
Expand All @@ -32,7 +141,7 @@ <h3 class="md-title">Dynamic Text Elements</h3>
<md-card-content>
<h3 class="md-title">Dynamic Number Elements</h3>
<md-divider></md-divider>
<md-tab-group md-stretch-tabs>
<md-tab-group md-stretch-tabs dynamicHeight>
<md-tab>
<ng-template md-tab-label>Demo</ng-template>
<td-dynamic-forms [elements]="numberElements">
Expand All @@ -59,7 +168,7 @@ <h3 class="md-title">Dynamic Number Elements</h3>
<md-card-content>
<h3 class="md-title">Dynamic Boolean Elements</h3>
<md-divider></md-divider>
<md-tab-group md-stretch-tabs>
<md-tab-group md-stretch-tabs dynamicHeight>
<md-tab>
<ng-template md-tab-label>Demo</ng-template>
<td-dynamic-forms [elements]="booleanElements">
Expand All @@ -86,7 +195,7 @@ <h3 class="md-title">Dynamic Boolean Elements</h3>
<md-card-content>
<h3 class="md-title">Dynamic Array Elements</h3>
<md-divider></md-divider>
<md-tab-group md-stretch-tabs>
<md-tab-group md-stretch-tabs dynamicHeight>
<md-tab>
<ng-template md-tab-label>Demo</ng-template>
<td-dynamic-forms [elements]="arrayElements">
Expand All @@ -113,7 +222,7 @@ <h3 class="md-title">Dynamic Array Elements</h3>
<md-card-content>
<h3 class="md-title">Dynamic File Input Element</h3>
<md-divider></md-divider>
<md-tab-group md-stretch-tabs>
<md-tab-group md-stretch-tabs dynamicHeight>
<md-tab>
<ng-template md-tab-label>Demo</ng-template>
<td-dynamic-forms [elements]="fileElements">
Expand All @@ -136,85 +245,5 @@ <h3 class="md-title">Dynamic File Input Element</h3>
</md-tab-group>
</md-card-content>
</md-card>
<md-card>
<md-card-title>TdDynamicFormsComponent</md-card-title>
<md-card-subtitle>How to use this component</md-card-subtitle>
<md-divider></md-divider>
<md-card-content>
<h2><code><![CDATA[<td-dynamic-forms>]]></code></h2>
<p>Use <code><![CDATA[<td-dynamic-forms>]]></code> element to generate a form dynamically.</p>
<p>Pass an array of javascript objects that implement [ITdDynamicElementConfig] with the information to be rendered to the [elements] attribute.</p>
<td-highlight lang="typescript">
<![CDATA[
export interface ITdDynamicElementConfig {
label?: string;
name: string;
type: TdDynamicType | TdDynamicElement;
required?: boolean;
min?: any;
max?: any;
default?: any;
}
]]>
</td-highlight>
<h3>Properties:</h3>
<p>The <code><![CDATA[<td-dynamic-forms>]]></code> component has {{dynamicFormsAttrs.length}} properties:</p>
<md-list>
<ng-template let-attr let-last="attr" ngFor [ngForOf]="dynamicFormsAttrs">
<a md-list-item layout-align="row">
<h3 md-line> {{attr.name}}: <span>{{attr.type}}</span></h3>
<p md-line> {{attr.description}} </p>
</a>
<md-divider *ngIf="!last"></md-divider>
</ng-template>
</md-list>
<h3>Example:</h3>
<p>HTML:</p>
<td-highlight lang="html">
<![CDATA[
<td-dynamic-forms [elements]="elements">
</td-dynamic-forms>
]]>
</td-highlight>
<p>Typescript:</p>
<td-highlight lang="typescript">
<![CDATA[
import { ITdDynamicElementConfig, TdDynamicElement, TdDynamicType } from '@covalent/dynamic-forms';
...
})
export class Demo {
elements: ITdDynamicElementConfig[] = [{
name: 'input-without-label',
type: TdDynamicElement.Input,
required: false,
}, {
name: 'input-with-label',
label: 'Input Label',
type: TdDynamicElement.Input,
required: true,
}, {
name: 'text-with-default',
type: TdDynamicType.Text,
required: false,
default: 'Default',
}];
}
]]>
</td-highlight>
<h3>Setup:</h3>
<p>Import the [CovalentDynamicFormsModule] in your NgModule:</p>
<td-highlight lang="typescript">
<![CDATA[
import { CovalentDynamicFormsModule } from '@covalent/dynamic-forms';
@NgModule({
imports: [
CovalentDynamicFormsModule,
...
],
...
})
export class MyModule {}
]]>
</td-highlight>
</md-card-content>
</md-card>

<td-readme-loader resourceUrl="platform/dynamic-forms/README.md"></td-readme-loader>
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,13 @@ export class DynamicFormsDemoComponent {
name: 'input',
type: TdDynamicElement.Input,
required: false,
flex: 50,
}, {
name: 'required-input',
label: 'Input Label',
type: TdDynamicElement.Input,
required: true,
flex: 50,
}, {
name: 'textarea',
type: TdDynamicElement.Textarea,
Expand All @@ -58,6 +60,7 @@ export class DynamicFormsDemoComponent {
type: TdDynamicType.Text,
required: false,
default: 'Default',
flex: 100,
}, {
name: 'required-password',
label: 'Password Label',
Expand Down Expand Up @@ -110,4 +113,53 @@ export class DynamicFormsDemoComponent {
label: 'Browse a file',
type: TdDynamicElement.FileInput,
}];

dynamicElements: ITdDynamicElementConfig[] = [{
name: 'element-0',
type: TdDynamicType.Text,
required: true,
flex: 80,
}, {
name: 'element-1',
type: TdDynamicType.Number,
required: false,
max: 30,
flex: 20,
}];

elementOptions: any[] = [
TdDynamicElement.Input,
TdDynamicType.Number,
TdDynamicElement.Password,
TdDynamicElement.Textarea,
TdDynamicElement.Slider,
TdDynamicElement.Checkbox,
TdDynamicElement.SlideToggle,
TdDynamicElement.FileInput,
];

showDynamicCode: boolean = false;

type: any;

count: number = 2;

isMinMaxSupported(type: TdDynamicElement | TdDynamicType): boolean {
return type === TdDynamicElement.Slider || type === TdDynamicType.Number;
}

addElement(): void {
if (this.type) {
this.dynamicElements.push({
name: 'element-' + this.count++,
type: this.type,
required: false,
});
this.type = undefined;
}
}

deleteElement(index: number): void {
this.dynamicElements.splice(index, 1);
}
}
7 changes: 6 additions & 1 deletion src/platform/dynamic-forms/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Properties:
| `value` | `get(): any` | Getter property for [value] of dynamic [FormGroup].
| `errors` | `get(): {[name: string]: any}` | Getter property for [errors] of dynamic [FormGroup].
| `controls` | `get(): {[key: string]: AbstractControl}` | Getter property for [controls] of dynamic [FormGroup].
| `refresh` | `function` | Refreshes the form and rerenders all validator/element modifications.


## Setup
Expand Down Expand Up @@ -54,7 +55,7 @@ Example for HTML usage:
```html
<td-dynamic-forms [elements]="elements">
</td-dynamic-forms>
```
```

```typescript
import { ITdDynamicElementConfig, TdDynamicElement, TdDynamicType } from '@covalent/dynamic-forms';
Expand All @@ -80,6 +81,10 @@ export class Demo {
required: true,
selections: ['A','B','C']
default: 'A',
}, {
name: 'file-input',
label: 'Label',
type: TdDynamicElement.FileInput,
}];
}
```
Loading

0 comments on commit e7be2a8

Please sign in to comment.