From 3cb219c5ab00efdd3bacd40006f23ecf781cb905 Mon Sep 17 00:00:00 2001 From: Torgeir Helgevold Date: Sat, 18 Jun 2016 12:00:04 -0400 Subject: [PATCH] docs(dynamic-cookbook): upgrade to use new forms api convert exiting to use deprecated name converted to new api text warnings fix plunker text test weak text space text lint order tweak --- .../cb-dynamic-form-deprecated/e2e-spec.ts | 27 ++++ .../ts/app/app.component.ts | 24 +++ .../app/dynamic-form-question.component.html | 17 ++ .../ts/app/dynamic-form-question.component.ts | 15 ++ .../ts/app/dynamic-form.component.html | 17 ++ .../ts/app/dynamic-form.component.ts | 30 ++++ .../cb-dynamic-form-deprecated/ts/app/main.ts | 6 + .../ts/app/question-base.ts | 25 +++ .../ts/app/question-control.service.ts | 18 +++ .../ts/app/question-dropdown.ts | 12 ++ .../ts/app/question-textbox.ts | 12 ++ .../ts/app/question.service.ts | 47 ++++++ .../ts/example-config.json | 0 .../cb-dynamic-form-deprecated/ts/index.html | 29 ++++ .../cb-dynamic-form-deprecated/ts/plnkr.json | 9 ++ .../cb-dynamic-form-deprecated/ts/sample.css | 7 + .../app/dynamic-form-question.component.html | 8 +- .../ts/app/dynamic-form-question.component.ts | 7 +- .../ts/app/dynamic-form.component.html | 2 +- .../ts/app/dynamic-form.component.ts | 10 +- .../_examples/cb-dynamic-form/ts/app/main.ts | 11 +- .../ts/app/question-control.service.ts | 14 +- .../ts/app/question.service.ts | 2 +- public/docs/_examples/package.json | 1 + public/docs/_examples/systemjs.config.js | 1 + .../docs/_examples/systemjs.config.plunker.js | 5 + public/docs/ts/latest/cookbook/_data.json | 2 +- .../cookbook/dynamic-form-deprecated.jade | 149 ++++++++++++++++++ .../docs/ts/latest/cookbook/dynamic-form.jade | 38 ++++- 29 files changed, 516 insertions(+), 29 deletions(-) create mode 100644 public/docs/_examples/cb-dynamic-form-deprecated/e2e-spec.ts create mode 100644 public/docs/_examples/cb-dynamic-form-deprecated/ts/app/app.component.ts create mode 100644 public/docs/_examples/cb-dynamic-form-deprecated/ts/app/dynamic-form-question.component.html create mode 100644 public/docs/_examples/cb-dynamic-form-deprecated/ts/app/dynamic-form-question.component.ts create mode 100644 public/docs/_examples/cb-dynamic-form-deprecated/ts/app/dynamic-form.component.html create mode 100644 public/docs/_examples/cb-dynamic-form-deprecated/ts/app/dynamic-form.component.ts create mode 100644 public/docs/_examples/cb-dynamic-form-deprecated/ts/app/main.ts create mode 100644 public/docs/_examples/cb-dynamic-form-deprecated/ts/app/question-base.ts create mode 100644 public/docs/_examples/cb-dynamic-form-deprecated/ts/app/question-control.service.ts create mode 100644 public/docs/_examples/cb-dynamic-form-deprecated/ts/app/question-dropdown.ts create mode 100644 public/docs/_examples/cb-dynamic-form-deprecated/ts/app/question-textbox.ts create mode 100644 public/docs/_examples/cb-dynamic-form-deprecated/ts/app/question.service.ts create mode 100644 public/docs/_examples/cb-dynamic-form-deprecated/ts/example-config.json create mode 100644 public/docs/_examples/cb-dynamic-form-deprecated/ts/index.html create mode 100644 public/docs/_examples/cb-dynamic-form-deprecated/ts/plnkr.json create mode 100644 public/docs/_examples/cb-dynamic-form-deprecated/ts/sample.css create mode 100644 public/docs/ts/latest/cookbook/dynamic-form-deprecated.jade diff --git a/public/docs/_examples/cb-dynamic-form-deprecated/e2e-spec.ts b/public/docs/_examples/cb-dynamic-form-deprecated/e2e-spec.ts new file mode 100644 index 0000000000..725022d0f1 --- /dev/null +++ b/public/docs/_examples/cb-dynamic-form-deprecated/e2e-spec.ts @@ -0,0 +1,27 @@ +/// +'use strict'; +/* tslint:disable:quotemark */ +describe('Dynamic Form Deprecated', function () { + + beforeAll(function () { + browser.get(''); + }); + + it('should submit form', function () { + let firstNameElement = element.all(by.css('input[id=firstName]')).get(0); + expect(firstNameElement.getAttribute('value')).toEqual('Bombasto'); + + let emailElement = element.all(by.css('input[id=emailAddress]')).get(0); + let email = 'test@test.com'; + emailElement.sendKeys(email); + expect(emailElement.getAttribute('value')).toEqual(email); + + element(by.css('select option[value="solid"]')).click(); + + let saveButton = element.all(by.css('button')).get(0); + saveButton.click().then(function(){ + expect(element(by.xpath("//strong[contains(text(),'Saved the following values')]")).isPresent()).toBe(true); + }); + }); + +}); diff --git a/public/docs/_examples/cb-dynamic-form-deprecated/ts/app/app.component.ts b/public/docs/_examples/cb-dynamic-form-deprecated/ts/app/app.component.ts new file mode 100644 index 0000000000..e51561770e --- /dev/null +++ b/public/docs/_examples/cb-dynamic-form-deprecated/ts/app/app.component.ts @@ -0,0 +1,24 @@ +// #docregion +import { Component } from '@angular/core'; + +import { DynamicFormComponent } from './dynamic-form.component'; +import { QuestionService } from './question.service'; + +@Component({ + selector: 'my-app', + template: ` +
+

Job Application for Heroes

+ +
+ `, + directives: [DynamicFormComponent], + providers: [QuestionService] +}) +export class AppComponent { + questions: any[]; + + constructor(service: QuestionService) { + this.questions = service.getQuestions(); + } +} diff --git a/public/docs/_examples/cb-dynamic-form-deprecated/ts/app/dynamic-form-question.component.html b/public/docs/_examples/cb-dynamic-form-deprecated/ts/app/dynamic-form-question.component.html new file mode 100644 index 0000000000..ceb2f41177 --- /dev/null +++ b/public/docs/_examples/cb-dynamic-form-deprecated/ts/app/dynamic-form-question.component.html @@ -0,0 +1,17 @@ + +
+ + +
+ + + + + +
+ +
{{question.label}} is required
+
diff --git a/public/docs/_examples/cb-dynamic-form-deprecated/ts/app/dynamic-form-question.component.ts b/public/docs/_examples/cb-dynamic-form-deprecated/ts/app/dynamic-form-question.component.ts new file mode 100644 index 0000000000..0593fd523e --- /dev/null +++ b/public/docs/_examples/cb-dynamic-form-deprecated/ts/app/dynamic-form-question.component.ts @@ -0,0 +1,15 @@ +// #docregion +import { Component, Input } from '@angular/core'; +import { ControlGroup } from '@angular/common'; + +import { QuestionBase } from './question-base'; + +@Component({ + selector: 'df-question', + templateUrl: 'app/dynamic-form-question.component.html' +}) +export class DynamicFormQuestionComponent { + @Input() question: QuestionBase; + @Input() form: ControlGroup; + get isValid() { return this.form.controls[this.question.key].valid; } +} diff --git a/public/docs/_examples/cb-dynamic-form-deprecated/ts/app/dynamic-form.component.html b/public/docs/_examples/cb-dynamic-form-deprecated/ts/app/dynamic-form.component.html new file mode 100644 index 0000000000..e8f14612c9 --- /dev/null +++ b/public/docs/_examples/cb-dynamic-form-deprecated/ts/app/dynamic-form.component.html @@ -0,0 +1,17 @@ + +
+
+ +
+ +
+ +
+ +
+
+ +
+ Saved the following values
{{payLoad}} +
+
diff --git a/public/docs/_examples/cb-dynamic-form-deprecated/ts/app/dynamic-form.component.ts b/public/docs/_examples/cb-dynamic-form-deprecated/ts/app/dynamic-form.component.ts new file mode 100644 index 0000000000..5d6eb833ed --- /dev/null +++ b/public/docs/_examples/cb-dynamic-form-deprecated/ts/app/dynamic-form.component.ts @@ -0,0 +1,30 @@ +// #docregion +import { Component, Input, OnInit } from '@angular/core'; +import { ControlGroup } from '@angular/common'; + +import { QuestionBase } from './question-base'; +import { QuestionControlService } from './question-control.service'; +import { DynamicFormQuestionComponent } from './dynamic-form-question.component'; + +@Component({ + selector: 'dynamic-form', + templateUrl: 'app/dynamic-form.component.html', + directives: [DynamicFormQuestionComponent], + providers: [QuestionControlService] +}) +export class DynamicFormComponent implements OnInit { + + @Input() questions: QuestionBase[] = []; + form: ControlGroup; + payLoad = ''; + + constructor(private qcs: QuestionControlService) { } + + ngOnInit() { + this.form = this.qcs.toControlGroup(this.questions); + } + + onSubmit() { + this.payLoad = JSON.stringify(this.form.value); + } +} diff --git a/public/docs/_examples/cb-dynamic-form-deprecated/ts/app/main.ts b/public/docs/_examples/cb-dynamic-form-deprecated/ts/app/main.ts new file mode 100644 index 0000000000..5a8d9c5044 --- /dev/null +++ b/public/docs/_examples/cb-dynamic-form-deprecated/ts/app/main.ts @@ -0,0 +1,6 @@ +import { bootstrap } from '@angular/platform-browser-dynamic'; + +import { AppComponent } from './app.component'; + +bootstrap(AppComponent, []) + .catch((err: any) => console.error(err)); diff --git a/public/docs/_examples/cb-dynamic-form-deprecated/ts/app/question-base.ts b/public/docs/_examples/cb-dynamic-form-deprecated/ts/app/question-base.ts new file mode 100644 index 0000000000..2b32b00f2a --- /dev/null +++ b/public/docs/_examples/cb-dynamic-form-deprecated/ts/app/question-base.ts @@ -0,0 +1,25 @@ +// #docregion +export class QuestionBase{ + value: T; + key: string; + label: string; + required: boolean; + order: number; + controlType: string; + + constructor(options: { + value?: T, + key?: string, + label?: string, + required?: boolean, + order?: number, + controlType?: string + } = {}) { + this.value = options.value; + this.key = options.key || ''; + this.label = options.label || ''; + this.required = !!options.required; + this.order = options.order === undefined ? 1 : options.order; + this.controlType = options.controlType || ''; + } +} diff --git a/public/docs/_examples/cb-dynamic-form-deprecated/ts/app/question-control.service.ts b/public/docs/_examples/cb-dynamic-form-deprecated/ts/app/question-control.service.ts new file mode 100644 index 0000000000..3d56b0c2f6 --- /dev/null +++ b/public/docs/_examples/cb-dynamic-form-deprecated/ts/app/question-control.service.ts @@ -0,0 +1,18 @@ +// #docregion +import { Injectable } from '@angular/core'; +import { FormBuilder, Validators } from '@angular/common'; +import { QuestionBase } from './question-base'; + +@Injectable() +export class QuestionControlService { + constructor(private fb: FormBuilder) { } + + toControlGroup(questions: QuestionBase[] ) { + let group = {}; + + questions.forEach(question => { + group[question.key] = question.required ? [question.value || '', Validators.required] : [question.value || '']; + }); + return this.fb.group(group); + } +} diff --git a/public/docs/_examples/cb-dynamic-form-deprecated/ts/app/question-dropdown.ts b/public/docs/_examples/cb-dynamic-form-deprecated/ts/app/question-dropdown.ts new file mode 100644 index 0000000000..35a9074c74 --- /dev/null +++ b/public/docs/_examples/cb-dynamic-form-deprecated/ts/app/question-dropdown.ts @@ -0,0 +1,12 @@ +// #docregion +import { QuestionBase } from './question-base'; + +export class DropdownQuestion extends QuestionBase { + controlType = 'dropdown'; + options: {key: string, value: string}[] = []; + + constructor(options: {} = {}) { + super(options); + this.options = options['options'] || []; + } +} diff --git a/public/docs/_examples/cb-dynamic-form-deprecated/ts/app/question-textbox.ts b/public/docs/_examples/cb-dynamic-form-deprecated/ts/app/question-textbox.ts new file mode 100644 index 0000000000..aaa7edf267 --- /dev/null +++ b/public/docs/_examples/cb-dynamic-form-deprecated/ts/app/question-textbox.ts @@ -0,0 +1,12 @@ +// #docregion +import { QuestionBase } from './question-base'; + +export class TextboxQuestion extends QuestionBase { + controlType = 'textbox'; + type: string; + + constructor(options: {} = {}) { + super(options); + this.type = options['type'] || ''; + } +} diff --git a/public/docs/_examples/cb-dynamic-form-deprecated/ts/app/question.service.ts b/public/docs/_examples/cb-dynamic-form-deprecated/ts/app/question.service.ts new file mode 100644 index 0000000000..ee169d0827 --- /dev/null +++ b/public/docs/_examples/cb-dynamic-form-deprecated/ts/app/question.service.ts @@ -0,0 +1,47 @@ +// #docregion +import { Injectable } from '@angular/core'; + +import { QuestionBase } from './question-base'; +import { TextboxQuestion } from './question-textbox'; +import { DropdownQuestion } from './question-dropdown'; + +@Injectable() +export class QuestionService { + + // Todo: get from a remote source of question metadata + // Todo: make asynchronous + getQuestions() { + + let questions: QuestionBase[] = [ + + new DropdownQuestion({ + key: 'brave', + label: 'Bravery Rating', + options: [ + {key: 'solid', value: 'Solid'}, + {key: 'great', value: 'Great'}, + {key: 'good', value: 'Good'}, + {key: 'unproven', value: 'Unproven'} + ], + order: 3 + }), + + new TextboxQuestion({ + key: 'firstName', + label: 'First name', + value: 'Bombasto', + required: true, + order: 1 + }), + + new TextboxQuestion({ + key: 'emailAddress', + label: 'Email', + type: 'email', + order: 2 + }) + ]; + + return questions.sort((a, b) => a.order - b.order); + } +} diff --git a/public/docs/_examples/cb-dynamic-form-deprecated/ts/example-config.json b/public/docs/_examples/cb-dynamic-form-deprecated/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/cb-dynamic-form-deprecated/ts/index.html b/public/docs/_examples/cb-dynamic-form-deprecated/ts/index.html new file mode 100644 index 0000000000..ec3e1cbbb2 --- /dev/null +++ b/public/docs/_examples/cb-dynamic-form-deprecated/ts/index.html @@ -0,0 +1,29 @@ + + + + + + Dynamic Form + + + + + + + + + + + + + + + + + + Loading app... + + + diff --git a/public/docs/_examples/cb-dynamic-form-deprecated/ts/plnkr.json b/public/docs/_examples/cb-dynamic-form-deprecated/ts/plnkr.json new file mode 100644 index 0000000000..5034d652aa --- /dev/null +++ b/public/docs/_examples/cb-dynamic-form-deprecated/ts/plnkr.json @@ -0,0 +1,9 @@ +{ + "description": "Dynamic Form Deprecated", + "files":[ + "!**/*.d.ts", + "!**/*.js", + "!**/*.[1].*" + ], + "tags":["cookbook"] +} diff --git a/public/docs/_examples/cb-dynamic-form-deprecated/ts/sample.css b/public/docs/_examples/cb-dynamic-form-deprecated/ts/sample.css new file mode 100644 index 0000000000..fe2cc28481 --- /dev/null +++ b/public/docs/_examples/cb-dynamic-form-deprecated/ts/sample.css @@ -0,0 +1,7 @@ +.errorMessage{ + color:red; +} + +.form-row{ + margin-top: 10px; +} \ No newline at end of file diff --git a/public/docs/_examples/cb-dynamic-form/ts/app/dynamic-form-question.component.html b/public/docs/_examples/cb-dynamic-form/ts/app/dynamic-form-question.component.html index ceb2f41177..9f1b8cd4a6 100644 --- a/public/docs/_examples/cb-dynamic-form/ts/app/dynamic-form-question.component.html +++ b/public/docs/_examples/cb-dynamic-form/ts/app/dynamic-form-question.component.html @@ -1,17 +1,17 @@ -
+
- - -
+
{{question.label}} is required
diff --git a/public/docs/_examples/cb-dynamic-form/ts/app/dynamic-form-question.component.ts b/public/docs/_examples/cb-dynamic-form/ts/app/dynamic-form-question.component.ts index 0593fd523e..3381d1c444 100644 --- a/public/docs/_examples/cb-dynamic-form/ts/app/dynamic-form-question.component.ts +++ b/public/docs/_examples/cb-dynamic-form/ts/app/dynamic-form-question.component.ts @@ -1,15 +1,16 @@ // #docregion import { Component, Input } from '@angular/core'; -import { ControlGroup } from '@angular/common'; +import { FormGroup, REACTIVE_FORM_DIRECTIVES } from '@angular/forms'; import { QuestionBase } from './question-base'; @Component({ selector: 'df-question', - templateUrl: 'app/dynamic-form-question.component.html' + templateUrl: 'app/dynamic-form-question.component.html', + directives: [REACTIVE_FORM_DIRECTIVES] }) export class DynamicFormQuestionComponent { @Input() question: QuestionBase; - @Input() form: ControlGroup; + @Input() form: FormGroup; get isValid() { return this.form.controls[this.question.key].valid; } } diff --git a/public/docs/_examples/cb-dynamic-form/ts/app/dynamic-form.component.html b/public/docs/_examples/cb-dynamic-form/ts/app/dynamic-form.component.html index e8f14612c9..717f09ff71 100644 --- a/public/docs/_examples/cb-dynamic-form/ts/app/dynamic-form.component.html +++ b/public/docs/_examples/cb-dynamic-form/ts/app/dynamic-form.component.html @@ -1,6 +1,6 @@
-
+
diff --git a/public/docs/_examples/cb-dynamic-form/ts/app/dynamic-form.component.ts b/public/docs/_examples/cb-dynamic-form/ts/app/dynamic-form.component.ts index 5d6eb833ed..15730806d1 100644 --- a/public/docs/_examples/cb-dynamic-form/ts/app/dynamic-form.component.ts +++ b/public/docs/_examples/cb-dynamic-form/ts/app/dynamic-form.component.ts @@ -1,27 +1,27 @@ // #docregion import { Component, Input, OnInit } from '@angular/core'; -import { ControlGroup } from '@angular/common'; +import { FormGroup, REACTIVE_FORM_DIRECTIVES } from '@angular/forms'; +import { DynamicFormQuestionComponent } from './dynamic-form-question.component'; import { QuestionBase } from './question-base'; import { QuestionControlService } from './question-control.service'; -import { DynamicFormQuestionComponent } from './dynamic-form-question.component'; @Component({ selector: 'dynamic-form', templateUrl: 'app/dynamic-form.component.html', - directives: [DynamicFormQuestionComponent], + directives: [DynamicFormQuestionComponent, REACTIVE_FORM_DIRECTIVES], providers: [QuestionControlService] }) export class DynamicFormComponent implements OnInit { @Input() questions: QuestionBase[] = []; - form: ControlGroup; + form: FormGroup; payLoad = ''; constructor(private qcs: QuestionControlService) { } ngOnInit() { - this.form = this.qcs.toControlGroup(this.questions); + this.form = this.qcs.toFormGroup(this.questions); } onSubmit() { diff --git a/public/docs/_examples/cb-dynamic-form/ts/app/main.ts b/public/docs/_examples/cb-dynamic-form/ts/app/main.ts index 5a8d9c5044..461ce210c6 100644 --- a/public/docs/_examples/cb-dynamic-form/ts/app/main.ts +++ b/public/docs/_examples/cb-dynamic-form/ts/app/main.ts @@ -1,6 +1,11 @@ -import { bootstrap } from '@angular/platform-browser-dynamic'; +// #docregion +import { bootstrap } from '@angular/platform-browser-dynamic'; +import { disableDeprecatedForms, provideForms } from '@angular/forms'; import { AppComponent } from './app.component'; -bootstrap(AppComponent, []) - .catch((err: any) => console.error(err)); +bootstrap(AppComponent, [ + disableDeprecatedForms(), + provideForms() +]) +.catch((err: any) => console.error(err)); diff --git a/public/docs/_examples/cb-dynamic-form/ts/app/question-control.service.ts b/public/docs/_examples/cb-dynamic-form/ts/app/question-control.service.ts index 3d56b0c2f6..1378ba8490 100644 --- a/public/docs/_examples/cb-dynamic-form/ts/app/question-control.service.ts +++ b/public/docs/_examples/cb-dynamic-form/ts/app/question-control.service.ts @@ -1,18 +1,20 @@ // #docregion import { Injectable } from '@angular/core'; -import { FormBuilder, Validators } from '@angular/common'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; + import { QuestionBase } from './question-base'; @Injectable() export class QuestionControlService { - constructor(private fb: FormBuilder) { } + constructor() { } - toControlGroup(questions: QuestionBase[] ) { - let group = {}; + toFormGroup(questions: QuestionBase[] ) { + let group: any = {}; questions.forEach(question => { - group[question.key] = question.required ? [question.value || '', Validators.required] : [question.value || '']; + group[question.key] = question.required ? new FormControl(question.value || '', Validators.required) + : new FormControl(question.value || ''); }); - return this.fb.group(group); + return new FormGroup(group); } } diff --git a/public/docs/_examples/cb-dynamic-form/ts/app/question.service.ts b/public/docs/_examples/cb-dynamic-form/ts/app/question.service.ts index ee169d0827..bb452cf5e6 100644 --- a/public/docs/_examples/cb-dynamic-form/ts/app/question.service.ts +++ b/public/docs/_examples/cb-dynamic-form/ts/app/question.service.ts @@ -1,9 +1,9 @@ // #docregion import { Injectable } from '@angular/core'; +import { DropdownQuestion } from './question-dropdown'; import { QuestionBase } from './question-base'; import { TextboxQuestion } from './question-textbox'; -import { DropdownQuestion } from './question-dropdown'; @Injectable() export class QuestionService { diff --git a/public/docs/_examples/package.json b/public/docs/_examples/package.json index 50622f9aaf..00a3eb533c 100644 --- a/public/docs/_examples/package.json +++ b/public/docs/_examples/package.json @@ -28,6 +28,7 @@ "@angular/common": "2.0.0-rc.2", "@angular/compiler": "2.0.0-rc.2", "@angular/core": "2.0.0-rc.2", + "@angular/forms": "0.1.0", "@angular/http": "2.0.0-rc.2", "@angular/platform-browser": "2.0.0-rc.2", "@angular/platform-browser-dynamic": "2.0.0-rc.2", diff --git a/public/docs/_examples/systemjs.config.js b/public/docs/_examples/systemjs.config.js index ca37f9e8e1..22b0c210cc 100644 --- a/public/docs/_examples/systemjs.config.js +++ b/public/docs/_examples/systemjs.config.js @@ -24,6 +24,7 @@ 'common', 'compiler', 'core', + 'forms', 'http', 'platform-browser', 'platform-browser-dynamic', diff --git a/public/docs/_examples/systemjs.config.plunker.js b/public/docs/_examples/systemjs.config.plunker.js index bf2fc3de68..cf1ca0486f 100644 --- a/public/docs/_examples/systemjs.config.plunker.js +++ b/public/docs/_examples/systemjs.config.plunker.js @@ -7,6 +7,7 @@ var ngVer = '@2.0.0-rc.2'; // lock in the angular package version; do not let it float to current! var routerVer = '@3.0.0-alpha.7'; // lock router version + var formsVer = '@0.1.0'; // lock forms version //map tells the System loader where to look for things var map = { @@ -14,6 +15,7 @@ '@angular': 'https://npmcdn.com/@angular', // sufficient if we didn't pin the version '@angular/router': 'https://npmcdn.com/@angular/router' + routerVer, + '@angular/forms': 'https://npmcdn.com/@angular/forms' + formsVer, 'angular2-in-memory-web-api': 'https://npmcdn.com/angular2-in-memory-web-api', // get latest 'rxjs': 'https://npmcdn.com/rxjs@5.0.0-beta.6', 'ts': 'https://npmcdn.com/plugin-typescript@4.0.10/lib/plugin.js', @@ -57,6 +59,9 @@ // No umd for router yet packages['@angular/router'] = { main: 'index.js', defaultExtension: 'js' }; + // Forms not on rc yet + packages['@angular/forms'] = { main: 'index.js', defaultExtension: 'js' }; + var config = { // DEMO ONLY! REAL CODE SHOULD NOT TRANSPILE IN THE BROWSER transpiler: 'ts', diff --git a/public/docs/ts/latest/cookbook/_data.json b/public/docs/ts/latest/cookbook/_data.json index 7043e62696..f6ef392936 100644 --- a/public/docs/ts/latest/cookbook/_data.json +++ b/public/docs/ts/latest/cookbook/_data.json @@ -26,7 +26,7 @@ "intro": "Techniques for Dependency Injection" }, - "dynamic-form": { + "dynamic-form-deprecated": { "title": "Dynamic Form", "intro": "Render dynamic forms with NgFormModel" }, diff --git a/public/docs/ts/latest/cookbook/dynamic-form-deprecated.jade b/public/docs/ts/latest/cookbook/dynamic-form-deprecated.jade new file mode 100644 index 0000000000..2efa5f8197 --- /dev/null +++ b/public/docs/ts/latest/cookbook/dynamic-form-deprecated.jade @@ -0,0 +1,149 @@ +include ../_util-fns + +.alert.is-important + :marked + This cookbook is using the deprecated forms API. + + We have created a new version of this cookbook using the new API here. + +:marked + We can't always justify the cost and time to build handcrafted forms, + especially if we'll need a great number of them, they're similar to each other, and they change frequently + to meet rapidly changing business and regulatory requirements. + + It may be more economical to create the forms dynamically, based on metadata that describe the business object model. + + In this cookbook we show how to use `ngFormModel` to dynamically render a simple form with different control types and validation. + It's a primitive start. + It might evolve to support a much richer variety of questions, more graceful rendering, and superior user experience. + All such greatness has humble beginnings. + + In our example we use a dynamic form to build an online application experience for heroes seeking employment. + The agency is constantly tinkering with the application process. + We can create the forms on the fly *without changing our application code*. + + +:marked + ## Table of contents + + [Question Model](#object-model) + + [Form Component](#form-component) + + [Questionnaire Metadata](#questionnaire-metadata) + + [Dynamic Template](#dynamic-template) + +:marked + **See the [live example](/resources/live-examples/cb-dynamic-form-deprecated/ts/plnkr.html)**. + +.l-main-section + +:marked + ## Question Model + + The first step is to define an object model that can describe all scenarios needed by the form functionality. + The hero application process involves a form with a lot of questions. + The "question" is the most fundamental object in the model. + + We have created `QuestionBase` as the most fundamental question class. + ++makeExample('cb-dynamic-form-deprecated/ts/app/question-base.ts','','app/question-base.ts') + +:marked + From this base we derived two new classes in `TextboxQuestion` and `DropdownQuestion` that represent Textbox and Dropdown questions. + The idea is that the form will be bound to specific question types and render the appropriate controls dynamically. + + `TextboxQuestion` supports multiple html5 types like text, email, url etc via the `type` property. + ++makeExample('cb-dynamic-form-deprecated/ts/app/question-textbox.ts',null,'app/question-textbox.ts')(format='.') + +:marked + `DropdownQuestion` presents a list of choices in a select box. + ++makeExample('cb-dynamic-form-deprecated/ts/app/question-dropdown.ts',null,'app/question-dropdown.ts')(format='.') + +:marked + Next we have defined `QuestionControlService`, a simple service for transforming our questions to an ngForm control group. + In a nutshell, the control group consumes the metadata from the question model and allows us to specify default values and validation rules. + ++makeExample('cb-dynamic-form-deprecated/ts/app/question-control.service.ts',null,'app/question-control.service.ts')(format='.') + + +:marked + ## Question form components + Now that we have defined the complete model we are ready to create components to represent the dynamic form. + +:marked + `DynamicFormComponent` is the entry point and the main container for the form. ++makeTabs( + `cb-dynamic-form-deprecated/ts/app/dynamic-form.component.html, + cb-dynamic-form-deprecated/ts/app/dynamic-form.component.ts`, + null, + `dynamic-form.component.html, + dynamic-form.component.ts` +) +:marked + It presents a list of questions, each question bound to a `` component element. + The `` tag matches the `DynamicFormQuestionComponent`, + the component responsible for rendering the details of each _individual_ question based on values in the data-bound question object. + ++makeTabs( + `cb-dynamic-form-deprecated/ts/app/dynamic-form-question.component.html, + cb-dynamic-form-deprecated/ts/app/dynamic-form-question.component.ts`, + null, + `dynamic-form-question.component.html, + dynamic-form-question.component.ts` +) +:marked + Notice this component can present any type of question in our model. + We only have two types of questions at this point but we can imagine many more. + The `ngSwitch` determines which type of question to display. + + In both components we're relying on Angular's **ngFormModel** to connect the template HTML to the + underlying control objects, populated from the question model with display and validation rules. + + +:marked + ## Questionnaire data +:marked + `DynamicFormComponent` expects the list of questions in the form of an array bound to `@Input() questions`. + + The set of questions we have defined for the job application is returned from the `QuestionService`. + In a real app we'd retrieve these questions from storage. + + The key point is that we control the hero job application questions entirely through the objects returned from `QuestionService`. + Questionnaire maintenance is a simple matter of adding, updating, and removing objects from the `questions` array. + ++makeExample('cb-dynamic-form-deprecated/ts/app/question.service.ts','','app/question.service.ts') + +:marked + Finally, we display an instance of the form in the `AppComponent` shell. + ++makeExample('cb-dynamic-form-deprecated/ts/app/app.component.ts','','app.component.ts') + + +:marked + ## Dynamic Template + Although in this example we're modelling a job application for heroes, there are no references to any specific hero question + outside the objects returned by `QuestionService`. + + This is very important since it allows us to repurpose the components for any type of survey + as long as it's compatible with our *question* object model. + The key is the dynamic data binding of metadata used to render the form + without making any hardcoded assumptions about specific questions. + In addition to control metadata, we are also adding validation dynamically. + + The *Save* button is disabled until the form is in a valid state. + When the form is valid, we can click *Save* and the app renders the current form values as JSON. + This proves that any user input is bound back to the data model. + Saving and retrieving the data is an exercise for another time. + +:marked + The final form looks like this: +figure.image-display + img(src="/resources/images/cookbooks/dynamic-form/dynamic-form.png" alt="Dynamic-Form") + + +:marked + [Back to top](#top) diff --git a/public/docs/ts/latest/cookbook/dynamic-form.jade b/public/docs/ts/latest/cookbook/dynamic-form.jade index 0f0e188edd..e4eb6af1d4 100644 --- a/public/docs/ts/latest/cookbook/dynamic-form.jade +++ b/public/docs/ts/latest/cookbook/dynamic-form.jade @@ -1,5 +1,11 @@ include ../_util-fns +.alert.is-important + :marked + This cookbook uses the new forms API. + + The old forms API is deprecated, but we still maintain a separate version of the cookbook using the deprecated forms API here. + :marked We can't always justify the cost and time to build handcrafted forms, especially if we'll need a great number of them, they're similar to each other, and they change frequently @@ -7,7 +13,7 @@ include ../_util-fns It may be more economical to create the forms dynamically, based on metadata that describe the business object model. - In this cookbook we show how to use `ngFormModel` to dynamically render a simple form with different control types and validation. + In this cookbook we show how to use `formGroup` to dynamically render a simple form with different control types and validation. It's a primitive start. It might evolve to support a much richer variety of questions, more graceful rendering, and superior user experience. All such greatness has humble beginnings. @@ -20,6 +26,8 @@ include ../_util-fns :marked ## Table of contents + [Bootstrap](#bootstrap) + [Question Model](#object-model) [Form Component](#form-component) @@ -31,12 +39,28 @@ include ../_util-fns :marked **See the [live example](/resources/live-examples/cb-dynamic-form/ts/plnkr.html)**. +.l-main-section + +:marked + ## Bootstrap + + During bootstrap we have to register the new forms module by calling `provideForms()` and pass the result to the provider array. + ++makeExample('cb-dynamic-form/ts/app/main.ts','','app/main.ts') + +:marked + The old forms API is going through a deprecation phase. During this transition Angular is supporting both form modules. + + To remind us that the old API is deprecated, Angular will print a warning message to the console. + + Since we are converting to the new API, and no longer need the old API, we call `disableDeprecatedForms()` to disable the old form functionality and the warning message. + .l-main-section :marked ## Question Model - The first step is to define an object model that can describe all scenarios needed by the form functionality. + The next step is to define an object model that can describe all scenarios needed by the form functionality. The hero application process involves a form with a lot of questions. The "question" is the most fundamental object in the model. @@ -58,8 +82,8 @@ include ../_util-fns +makeExample('cb-dynamic-form/ts/app/question-dropdown.ts',null,'app/question-dropdown.ts')(format='.') :marked - Next we have defined `QuestionControlService`, a simple service for transforming our questions to an ngForm control group. - In a nutshell, the control group consumes the metadata from the question model and allows us to specify default values and validation rules. + Next we have defined `QuestionControlService`, a simple service for transforming our questions to a `FormGroup`. + In a nutshell, the form group consumes the metadata from the question model and allows us to specify default values and validation rules. +makeExample('cb-dynamic-form/ts/app/question-control.service.ts',null,'app/question-control.service.ts')(format='.') @@ -94,9 +118,13 @@ include ../_util-fns We only have two types of questions at this point but we can imagine many more. The `ngSwitch` determines which type of question to display. - In both components we're relying on Angular's **ngFormModel** to connect the template HTML to the + In both components we're relying on Angular's **formGroup** to connect the template HTML to the underlying control objects, populated from the question model with display and validation rules. + `formControlName` and `formGroup` have to be registered as directives before we can use them in our templates. + + It turns out we get access to all form directives by importing and registering `REACTIVE_FORM_DIRECTIVES`. + :marked ## Questionnaire data