Skip to content
This repository was archived by the owner on Dec 4, 2017. It is now read-only.

Commit db7fba8

Browse files
thelgevoldwardbell
authored andcommitted
docs(cb-dynamic-form): new cookbook on dyn form gen with NgFormModel
1 parent b3eb189 commit db7fba8

20 files changed

+448
-0
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
describe('Dynamic Form', function () {
2+
3+
beforeAll(function () {
4+
browser.get('');
5+
});
6+
7+
it('should submit form', function () {
8+
var firstNameElement = element.all(by.css('input[id=firstName]')).get(0);
9+
expect(firstNameElement.getAttribute('value')).toEqual('Bombasto');
10+
11+
var emailElement = element.all(by.css('input[id=emailAddress]')).get(0);
12+
var email = '[email protected]';
13+
emailElement.sendKeys(email);
14+
expect(emailElement.getAttribute('value')).toEqual(email);
15+
16+
element(by.css('select option[value="solid"]')).click()
17+
18+
var saveButton = element.all(by.css('button')).get(0);
19+
saveButton.click().then(function(){
20+
expect(element(by.xpath("//strong[contains(text(),'Saved the following values')]")).isPresent()).toBe(true);
21+
});
22+
});
23+
24+
});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
**/*.js
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// #docregion
2+
import {Component} from 'angular2/core'
3+
import {DynamicForm} from './dynamic-form.component';
4+
import {QuestionService} from './question.service';
5+
6+
@Component({
7+
selector: 'my-app',
8+
template: `
9+
<div>
10+
<h2>Job Application for Heroes</h2>
11+
<dynamic-form [questions]="questions"></dynamic-form>
12+
</div>
13+
`,
14+
directives: [DynamicForm],
15+
providers: [QuestionService]
16+
})
17+
export class AppComponent {
18+
questions:any[]
19+
20+
constructor(service: QuestionService) {
21+
this.questions = service.getQuestions();
22+
}
23+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<!-- #docregion -->
2+
<div [ngFormModel]="form">
3+
<div class="formHeading">{{question.label}}</div>
4+
5+
<div [ngSwitch]="question.controlType">
6+
7+
<input *ngSwitchWhen="'textbox'" [ngControl]="question.key"
8+
[id]="question.key" [type]="question.type">
9+
10+
<select *ngSwitchWhen="'dropdown'" [ngControl]="question.key">
11+
<option *ngFor="#opt of question.options" [value]="opt.key">{{opt.value}}</option>
12+
</select>
13+
14+
</div>
15+
16+
<div class="errorMessage" *ngIf="!isValid">{{question.label}} is required</div>
17+
</div>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// #docregion
2+
import {Component, Input} from 'angular2/core';
3+
import {ControlGroup} from 'angular2/common';
4+
import {QuestionBase} from './question-base';
5+
6+
@Component({
7+
selector:'df-question',
8+
templateUrl:'app/dynamic-form-question.component.html'
9+
})
10+
export class DynamicFormQuestionComponent {
11+
@Input() question:QuestionBase<any>;
12+
@Input() form:ControlGroup;
13+
get isValid() { return this.form.controls[this.question.key].valid; }
14+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<!-- #docregion -->
2+
<div>
3+
<form (ngSubmit)="onSubmit()" [ngFormModel]="form">
4+
5+
<div *ngFor="#question of questions" class="form-row">
6+
<df-question [question]="question" [form]="form"></df-question>
7+
</div>
8+
9+
<div class="form-row">
10+
<button type="submit" [disabled]="!form.valid">Save</button>
11+
</div>
12+
</form>
13+
14+
<div *ngIf="payLoad" class="form-row">
15+
<strong>Saved the following values</strong><br>{{payLoad}}
16+
</div>
17+
</div>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// #docregion
2+
import {Component, Input, OnInit} from 'angular2/core';
3+
import {ControlGroup} from 'angular2/common';
4+
5+
import {QuestionBase} from './question-base';
6+
import {QuestionControlService} from './question-control.service';
7+
import {DynamicFormQuestionComponent} from './dynamic-form-question.component';
8+
9+
@Component({
10+
selector:'dynamic-form',
11+
templateUrl:'app/dynamic-form.component.html',
12+
directives: [DynamicFormQuestionComponent],
13+
providers: [QuestionControlService]
14+
})
15+
export class DynamicForm {
16+
17+
@Input() questions: QuestionBase<any>[] = [];
18+
form: ControlGroup;
19+
payLoad = '';
20+
21+
constructor(private _qcs: QuestionControlService) { }
22+
23+
ngOnInit(){
24+
this.form = this._qcs.toControlGroup(this.questions);
25+
}
26+
27+
onSubmit() {
28+
this.payLoad = JSON.stringify(this.form.value);
29+
}
30+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import {bootstrap} from 'angular2/platform/browser';
2+
import {AppComponent} from './app.component';
3+
4+
bootstrap(AppComponent, [])
5+
.catch((err:any) => console.error(err));
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// #docregion
2+
export class QuestionBase<T>{
3+
value:T;
4+
key:string;
5+
label:string;
6+
required:boolean;
7+
order:number;
8+
controlType:string;
9+
10+
constructor(options:{
11+
value?:T,
12+
key?:string,
13+
label?:string,
14+
required?:boolean,
15+
order?:number,
16+
controlType?:string
17+
} = {}){
18+
this.value = options.value;
19+
this.key = options.key || '';
20+
this.label = options.label || '';
21+
this.required = !!options.required;
22+
this.order = options.order === undefined ? 1 : options.order;
23+
this.controlType = options.controlType || '';
24+
}
25+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// #docregion
2+
import {Injectable} from 'angular2/core';
3+
import {ControlGroup, FormBuilder, Validators} from 'angular2/common';
4+
import {QuestionBase} from './question-base';
5+
6+
@Injectable()
7+
export class QuestionControlService {
8+
constructor(private _fb:FormBuilder){ }
9+
10+
toControlGroup(questions:QuestionBase<any>[] ) {
11+
let group = {};
12+
13+
questions.forEach(question => {
14+
group[question.key] = question.required ? [question.value || '', Validators.required] : [];
15+
});
16+
return this._fb.group(group);
17+
}
18+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// #docregion
2+
import {QuestionBase} from './question-base';
3+
4+
export class DropdownQuestion extends QuestionBase<string>{
5+
controlType = 'dropdown';
6+
options:{key:string, value:string}[] = [];
7+
8+
constructor(options:{} = {}){
9+
super(options);
10+
this.options = options['options'] || [];
11+
}
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// #docregion
2+
import {QuestionBase} from './question-base';
3+
4+
export class TextboxQuestion extends QuestionBase<string>{
5+
controlType = 'textbox';
6+
type:string;
7+
8+
constructor(options:{} = {}){
9+
super(options);
10+
this.type = options['type'] || '';
11+
}
12+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// #docregion
2+
import {Injectable} from 'angular2/core';
3+
import {QuestionBase} from './question-base';
4+
import {DynamicForm} from './dynamic-form.component';
5+
import {TextboxQuestion} from './question-textbox';
6+
import {DropdownQuestion} from './question-dropdown';
7+
8+
@Injectable()
9+
export class QuestionService {
10+
11+
// Todo: get from a remote source of question metadata
12+
// Todo: make asynchronous
13+
getQuestions() {
14+
15+
let questions:QuestionBase<any>[] = [
16+
17+
new DropdownQuestion({
18+
key:'brave',
19+
label: 'Bravery Rating',
20+
options: [
21+
{key:'solid', value:'Solid'},
22+
{key:'great', value:'Great'},
23+
{key:'good', value:'Good'},
24+
{key:'unproven',value:'Unproven'}
25+
],
26+
order: 3
27+
}),
28+
29+
new TextboxQuestion({
30+
key:'firstName',
31+
label:'First name',
32+
value:'Bombasto',
33+
required: true,
34+
order: 1
35+
}),
36+
37+
new TextboxQuestion({
38+
key:'emailAddress',
39+
label:'Email',
40+
type: 'email',
41+
order: 2
42+
})
43+
];
44+
45+
return questions.sort((a,b) => a.order - b.order);
46+
}
47+
}

public/docs/_examples/cb-dynamic-form/ts/example-config.json

Whitespace-only changes.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<base href="/">
5+
<title>Dynamic Form</title>
6+
<meta name="viewport" content="width=device-width, initial-scale=1">
7+
<!-- #docregion style -->
8+
<link rel="stylesheet" href="styles.css">
9+
<link rel="stylesheet" href="sample.css">
10+
<!-- #enddocregion style -->
11+
12+
<!-- IE required polyfills, in this exact order -->
13+
<script src="node_modules/es6-shim/es6-shim.min.js"></script>
14+
<script src="node_modules/systemjs/dist/system-polyfills.js"></script>
15+
<script src="node_modules/angular2/es6/dev/src/testing/shims_for_IE.js"></script>
16+
17+
<script src="node_modules/angular2/bundles/angular2-polyfills.js"></script>
18+
<script src="node_modules/systemjs/dist/system.src.js"></script>
19+
<script src="node_modules/rxjs/bundles/Rx.js"></script>
20+
<script src="node_modules/angular2/bundles/angular2.dev.js"></script>
21+
<script>
22+
System.config({
23+
packages: {
24+
app: {
25+
format: 'register',
26+
defaultExtension: 'js'
27+
}
28+
}
29+
});
30+
System.import('app/main')
31+
.then(null, console.error.bind(console));
32+
</script>
33+
</head>
34+
35+
<body>
36+
<my-app>Loading app...</my-app>
37+
</body>
38+
39+
</html>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"description": "Dynamic Form",
3+
"files":[
4+
"!**/*.d.ts",
5+
"!**/*.js",
6+
"!**/*.[1].*"
7+
],
8+
"tags":["cookbook"]
9+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.errorMessage{
2+
color:red;
3+
}
4+
5+
.form-row{
6+
margin-top: 10px;
7+
}

public/docs/ts/latest/cookbook/_data.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,10 @@
1414
"component-communication": {
1515
"title": "Component Interaction",
1616
"description": "Share information between different directives and components"
17+
},
18+
19+
"dynamic-form": {
20+
"title": "Dynamic Form",
21+
"description": "Render dynamic forms with NgFormModel"
1722
}
1823
}

0 commit comments

Comments
 (0)