Merge remote-tracking branch 'origin/master'
# Conflicts: # README.md # public/docs/ts/latest/_data.json # public/docs/ts/latest/cookbook/_data.json # public/docs/ts/latest/cookbook/dynamic-form.jade # public/docs/ts/latest/guide/_data.json # public/docs/ts/latest/guide/forms.jade # public/docs/ts/latest/guide/style-guide.jade
This commit is contained in:
commit
bb9adc3eba
|
@ -7,6 +7,7 @@ os:
|
|||
- linux
|
||||
env:
|
||||
global:
|
||||
- DBUS_SESSION_BUS_ADDRESS=/dev/null
|
||||
- DISPLAY=:99.0
|
||||
- CHROME_BIN=chromium-browser
|
||||
matrix:
|
||||
|
|
|
@ -37,7 +37,7 @@ var DOCS_PATH = path.join(PUBLIC_PATH, 'docs');
|
|||
|
||||
var EXAMPLES_PATH = path.join(DOCS_PATH, '_examples');
|
||||
var EXAMPLES_PROTRACTOR_PATH = path.join(EXAMPLES_PATH, '_protractor');
|
||||
var NOT_API_DOCS_GLOB = path.join(PUBLIC_PATH, './{docs/*/latest/!(api),!(docs)}/**/*');
|
||||
var NOT_API_DOCS_GLOB = path.join(PUBLIC_PATH, './{docs/*/latest/!(api),!(docs)}/**/*.*');
|
||||
var RESOURCES_PATH = path.join(PUBLIC_PATH, 'resources');
|
||||
var LIVE_EXAMPLES_PATH = path.join(RESOURCES_PATH, 'live-examples');
|
||||
|
||||
|
@ -991,6 +991,7 @@ function devGuideExamplesWatch(shredOptions, postShredAction) {
|
|||
// gulp.watch([includePattern, excludePattern], {readDelay: 500}, function (event, done) {
|
||||
var ignoreThese = [ '**/node_modules/**', '**/_fragments/**', '**/dist/**', '**/typings/**',
|
||||
'**/dart/.pub/**', '**/dart/build/**', '**/dart/packages/**'];
|
||||
ignoreThese = ignoreThese.concat(_exampleBoilerplateFiles.map((file) => `public/docs/_examples/*/*/${file}`));
|
||||
var files = globby.sync( [includePattern], { ignore: ignoreThese });
|
||||
gulp.watch([files], {readDelay: 500}, function (event, done) {
|
||||
gutil.log('Dev Guide example changed')
|
||||
|
@ -1005,7 +1006,8 @@ function devGuideSharedJadeWatch(shredOptions, postShredAction) {
|
|||
// removed this version because gulp.watch has the same glob issue that dgeni has.
|
||||
// var excludePattern = '!' + path.join(shredOptions.jadeDir, '**/node_modules/**/*.*');
|
||||
// gulp.watch([includePattern, excludePattern], {readDelay: 500}, function (event, done) {
|
||||
var files = globby.sync( [includePattern], { ignore: [ '**/node_modules/**', '**/_examples/**', '**/_fragments/**']});
|
||||
var ignoreThese = [ '**/node_modules/**', '**/_examples/**', '**/_fragments/**', '**/latest/api/**' ];
|
||||
var files = globby.sync( [includePattern], { ignore: ignoreThese});
|
||||
gulp.watch([files], {readDelay: 500}, function (event, done) {
|
||||
gutil.log('Dev Guide jade file changed')
|
||||
gutil.log('Event type: ' + event.type); // added, changed, or deleted
|
||||
|
|
41
harp.json
41
harp.json
|
@ -88,14 +88,6 @@
|
|||
"type": "Google"
|
||||
},
|
||||
|
||||
"brianford": {
|
||||
"name": "Brian Ford",
|
||||
"picture": "/resources/images/bios/brian-ford.jpg",
|
||||
"twitter": "briantford",
|
||||
"bio": "Brian works on the AngularJS core team at Google where he tries his very best to make computers do the right thing.",
|
||||
"type": "Google"
|
||||
},
|
||||
|
||||
"rado": {
|
||||
"name": "Rado Kirov",
|
||||
"picture": "/resources/images/bios/rado.jpg",
|
||||
|
@ -294,6 +286,15 @@
|
|||
"bio": "Stephen is a Developer Advocate working on the Angular team. Before joining Google, he was a Google Expert. Stephen loves to help enterprises use technology more effectively.",
|
||||
"type": "Google"
|
||||
},
|
||||
"robwormald": {
|
||||
"name": "Rob Wormald",
|
||||
"picture": "/resources/images/bios/rob-wormald.jpg",
|
||||
"twitter": "robwormald",
|
||||
"website": "http://github.com/robwormald",
|
||||
"bio": "Rob is a Developer Advocate on the Angular team at Google. He's the Angular team's resident reactive programming geek and founded the Reactive Extensions for Angular project, ngrx.",
|
||||
"type": "Google"
|
||||
},
|
||||
|
||||
"pawel": {
|
||||
"name": "Pawel Kozlowski",
|
||||
"picture": "/resources/images/bios/pawel.jpg",
|
||||
|
@ -422,6 +423,30 @@
|
|||
"website": "http://angular-tips.com",
|
||||
"bio": "Jesus is an open source lover, a book author and editor, and AngularUI lead developer. He is currently a core contributor to the UI Bootstrap project.",
|
||||
"type": "Community"
|
||||
},
|
||||
"torgeirhelgevold": {
|
||||
"name": "Torgeir Helgevold",
|
||||
"picture": "/resources/images/bios/torgeirhelgevold.jpg",
|
||||
"twitter": "helgevold",
|
||||
"website": "http://www.syntaxsuccess.com",
|
||||
"bio": "Torgeir (Tor) is a front-end architect with a passion for JavaScript development. He is also an author for angular.io and an active tech blogger.",
|
||||
"type": "Community"
|
||||
},
|
||||
"fatimaremtullah": {
|
||||
"name": "Fatima Remtullah",
|
||||
"picture": "/resources/images/bios/fatima.jpg",
|
||||
"twitter": "amitafr",
|
||||
"website": "http://www.amitafremtullah.com",
|
||||
"bio": "Fatima is a Product Designer and Front-End Developer. When she is not nerding out she is probably eating an abundance of cookies.",
|
||||
"type": "Community"
|
||||
},
|
||||
"eric": {
|
||||
"name": "Eric Jimenez",
|
||||
"picture": "/resources/images/bios/eric.jpg",
|
||||
"twitter": "_ericjim",
|
||||
"website": "http://eric.to/",
|
||||
"bio": "Eric is a gamer, writer, and programmer.",
|
||||
"type": "Community"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"globalDependencies": {
|
||||
"angular-protractor": "registry:dt/angular-protractor#1.5.0+20160425143459",
|
||||
"jasmine": "registry:dt/jasmine#2.2.0+20160505161446",
|
||||
"node": "registry:dt/node#6.0.0+20160613154055",
|
||||
"jasmine": "registry:dt/jasmine#2.2.0+20160621224255",
|
||||
"node": "registry:dt/node#6.0.0+20160621231320",
|
||||
"selenium-webdriver": "registry:dt/selenium-webdriver#2.44.0+20160317120654"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
/// <reference path='../_protractor/e2e.d.ts' />
|
||||
'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);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
|
@ -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: `
|
||||
<div>
|
||||
<h2>Job Application for Heroes</h2>
|
||||
<dynamic-form [questions]="questions"></dynamic-form>
|
||||
</div>
|
||||
`,
|
||||
directives: [DynamicFormComponent],
|
||||
providers: [QuestionService]
|
||||
})
|
||||
export class AppComponent {
|
||||
questions: any[];
|
||||
|
||||
constructor(service: QuestionService) {
|
||||
this.questions = service.getQuestions();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<!-- #docregion -->
|
||||
<div [ngFormModel]="form">
|
||||
<label [attr.for]="question.key">{{question.label}}</label>
|
||||
|
||||
<div [ngSwitch]="question.controlType">
|
||||
|
||||
<input *ngSwitchWhen="'textbox'" [ngControl]="question.key"
|
||||
[id]="question.key" [type]="question.type">
|
||||
|
||||
<select [id]="question.key" *ngSwitchWhen="'dropdown'" [ngControl]="question.key">
|
||||
<option *ngFor="let opt of question.options" [value]="opt.key">{{opt.value}}</option>
|
||||
</select>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="errorMessage" *ngIf="!isValid">{{question.label}} is required</div>
|
||||
</div>
|
|
@ -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<any>;
|
||||
@Input() form: ControlGroup;
|
||||
get isValid() { return this.form.controls[this.question.key].valid; }
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<!-- #docregion -->
|
||||
<div>
|
||||
<form (ngSubmit)="onSubmit()" [ngFormModel]="form">
|
||||
|
||||
<div *ngFor="let question of questions" class="form-row">
|
||||
<df-question [question]="question" [form]="form"></df-question>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<button type="submit" [disabled]="!form.valid">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div *ngIf="payLoad" class="form-row">
|
||||
<strong>Saved the following values</strong><br>{{payLoad}}
|
||||
</div>
|
||||
</div>
|
|
@ -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<any>[] = [];
|
||||
form: ControlGroup;
|
||||
payLoad = '';
|
||||
|
||||
constructor(private qcs: QuestionControlService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.form = this.qcs.toControlGroup(this.questions);
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
this.payLoad = JSON.stringify(this.form.value);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import { bootstrap } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
bootstrap(AppComponent, [])
|
||||
.catch((err: any) => console.error(err));
|
|
@ -0,0 +1,25 @@
|
|||
// #docregion
|
||||
export class QuestionBase<T>{
|
||||
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 || '';
|
||||
}
|
||||
}
|
|
@ -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<any>[] ) {
|
||||
let group = {};
|
||||
|
||||
questions.forEach(question => {
|
||||
group[question.key] = question.required ? [question.value || '', Validators.required] : [question.value || ''];
|
||||
});
|
||||
return this.fb.group(group);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
// #docregion
|
||||
import { QuestionBase } from './question-base';
|
||||
|
||||
export class DropdownQuestion extends QuestionBase<string> {
|
||||
controlType = 'dropdown';
|
||||
options: {key: string, value: string}[] = [];
|
||||
|
||||
constructor(options: {} = {}) {
|
||||
super(options);
|
||||
this.options = options['options'] || [];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
// #docregion
|
||||
import { QuestionBase } from './question-base';
|
||||
|
||||
export class TextboxQuestion extends QuestionBase<string> {
|
||||
controlType = 'textbox';
|
||||
type: string;
|
||||
|
||||
constructor(options: {} = {}) {
|
||||
super(options);
|
||||
this.type = options['type'] || '';
|
||||
}
|
||||
}
|
|
@ -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<any>[] = [
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<base href="/">
|
||||
<title>Dynamic Form</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<!-- #docregion style -->
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="sample.css">
|
||||
<!-- #enddocregion style -->
|
||||
|
||||
<!-- Polyfill(s) for older browsers -->
|
||||
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||
|
||||
<script src="node_modules/zone.js/dist/zone.js"></script>
|
||||
<script src="node_modules/reflect-metadata/Reflect.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
|
||||
<script src="systemjs.config.js"></script>
|
||||
<script>
|
||||
System.import('app').catch(function(err){ console.error(err); });
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<my-app>Loading app...</my-app>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"description": "Dynamic Form Deprecated",
|
||||
"files":[
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js",
|
||||
"!**/*.[1].*"
|
||||
],
|
||||
"tags":["cookbook"]
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
.errorMessage{
|
||||
color:red;
|
||||
}
|
||||
|
||||
.form-row{
|
||||
margin-top: 10px;
|
||||
}
|
|
@ -1,17 +1,17 @@
|
|||
<!-- #docregion -->
|
||||
<div [ngFormModel]="form">
|
||||
<div [formGroup]="form">
|
||||
<label [attr.for]="question.key">{{question.label}}</label>
|
||||
|
||||
<div [ngSwitch]="question.controlType">
|
||||
|
||||
<input *ngSwitchWhen="'textbox'" [ngControl]="question.key"
|
||||
<input *ngSwitchCase="'textbox'" [formControlName]="question.key"
|
||||
[id]="question.key" [type]="question.type">
|
||||
|
||||
<select [id]="question.key" *ngSwitchWhen="'dropdown'" [ngControl]="question.key">
|
||||
<select [id]="question.key" *ngSwitchCase="'dropdown'" [formControlName]="question.key">
|
||||
<option *ngFor="let opt of question.options" [value]="opt.key">{{opt.value}}</option>
|
||||
</select>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="errorMessage" *ngIf="!isValid">{{question.label}} is required</div>
|
||||
</div>
|
||||
|
|
|
@ -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<any>;
|
||||
@Input() form: ControlGroup;
|
||||
@Input() form: FormGroup;
|
||||
get isValid() { return this.form.controls[this.question.key].valid; }
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!-- #docregion -->
|
||||
<div>
|
||||
<form (ngSubmit)="onSubmit()" [ngFormModel]="form">
|
||||
<form (ngSubmit)="onSubmit()" [formGroup]="form">
|
||||
|
||||
<div *ngFor="let question of questions" class="form-row">
|
||||
<df-question [question]="question" [form]="form"></df-question>
|
||||
|
|
|
@ -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<any>[] = [];
|
||||
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() {
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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<any>[] ) {
|
||||
let group = {};
|
||||
toFormGroup(questions: QuestionBase<any>[] ) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
/// <reference path='../_protractor/e2e.d.ts' />
|
||||
'use strict';
|
||||
describeIf(browser.appIsTs || browser.appIsJs, 'Forms (Deprecated) Tests', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
browser.get('');
|
||||
});
|
||||
|
||||
it('should display correct title', function () {
|
||||
expect(element.all(by.css('h1')).get(0).getText()).toEqual('Hero Form');
|
||||
});
|
||||
|
||||
|
||||
it('should not display message before submit', function () {
|
||||
let ele = element(by.css('h2'));
|
||||
expect(ele.isDisplayed()).toBe(false);
|
||||
});
|
||||
|
||||
it('should hide form after submit', function () {
|
||||
let ele = element.all(by.css('h1')).get(0);
|
||||
expect(ele.isDisplayed()).toBe(true);
|
||||
let b = element.all(by.css('button[type=submit]')).get(0);
|
||||
b.click().then(function() {
|
||||
expect(ele.isDisplayed()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should display message after submit', function () {
|
||||
let b = element.all(by.css('button[type=submit]')).get(0);
|
||||
b.click().then(function() {
|
||||
expect(element(by.css('h2')).getText()).toContain('You submitted the following');
|
||||
});
|
||||
});
|
||||
|
||||
it('should hide form after submit', function () {
|
||||
let alterEgoEle = element.all(by.css('input[ngcontrol=alterEgo]')).get(0);
|
||||
expect(alterEgoEle.isDisplayed()).toBe(true);
|
||||
let submitButtonEle = element.all(by.css('button[type=submit]')).get(0);
|
||||
submitButtonEle.click().then(function() {
|
||||
expect(alterEgoEle.isDisplayed()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should reflect submitted data after submit', function () {
|
||||
let test = 'testing 1 2 3';
|
||||
let newValue: string;
|
||||
let alterEgoEle = element.all(by.css('input[ngcontrol=alterEgo]')).get(0);
|
||||
alterEgoEle.getAttribute('value').then(function(value) {
|
||||
// alterEgoEle.sendKeys(test);
|
||||
sendKeys(alterEgoEle, test);
|
||||
newValue = value + test;
|
||||
expect(alterEgoEle.getAttribute('value')).toEqual(newValue);
|
||||
}).then(function() {
|
||||
let b = element.all(by.css('button[type=submit]')).get(0);
|
||||
return b.click();
|
||||
}).then(function() {
|
||||
let alterEgoTextEle = element(by.cssContainingText('div', 'Alter Ego'));
|
||||
expect(alterEgoTextEle.isPresent()).toBe(true, 'cannot locate "Alter Ego" label');
|
||||
let divEle = element(by.cssContainingText('div', newValue));
|
||||
expect(divEle.isPresent()).toBe(true, 'cannot locate div with this text: ' + newValue);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// #docregion
|
||||
(function(app) {
|
||||
app.AppComponent = ng.core
|
||||
.Component({
|
||||
selector: 'my-app',
|
||||
template: '<hero-form></hero-form>',
|
||||
directives: [app.HeroFormComponent]
|
||||
})
|
||||
.Class({
|
||||
constructor: function() {}
|
||||
});
|
||||
})(window.app || (window.app = {}));
|
|
@ -0,0 +1,195 @@
|
|||
<!-- #docplaster -->
|
||||
<!-- #docregion final -->
|
||||
<div class="container">
|
||||
<!-- #docregion edit-div -->
|
||||
<div [hidden]="submitted">
|
||||
<h1>Hero Form</h1>
|
||||
<!-- #docregion ngSubmit -->
|
||||
<form (ngSubmit)="onSubmit()" #heroForm="ngForm">
|
||||
<!-- #enddocregion ngSubmit -->
|
||||
<!-- #enddocregion edit-div -->
|
||||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
<!-- #docregion name-with-error-msg -->
|
||||
<input type="text" class="form-control" required
|
||||
[(ngModel)]="model.name"
|
||||
ngControl="name" #name="ngForm" >
|
||||
<div [hidden]="name.valid" class="alert alert-danger">
|
||||
Name is required
|
||||
</div>
|
||||
<!-- #enddocregion name-with-error-msg -->
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="alterEgo">Alter Ego</label>
|
||||
<input type="text" class="form-control"
|
||||
[(ngModel)]="model.alterEgo"
|
||||
ngControl="alterEgo" >
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="power">Hero Power</label>
|
||||
<select class="form-control" required
|
||||
[(ngModel)]="model.power"
|
||||
ngControl="power" #power="ngForm" >
|
||||
<option *ngFor="let p of powers" [value]="p">{{p}}</option>
|
||||
</select>
|
||||
<div [hidden]="power.valid" class="alert alert-danger">
|
||||
Power is required
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- #docregion submit-button -->
|
||||
<button type="submit" class="btn btn-default"
|
||||
[disabled]="!heroForm.form.valid">Submit</button>
|
||||
<!-- #enddocregion submit-button -->
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- #docregion submitted -->
|
||||
<div [hidden]="!submitted">
|
||||
<h2>You submitted the following:</h2>
|
||||
<div class="row">
|
||||
<div class="col-xs-3">Name</div>
|
||||
<div class="col-xs-9 pull-left">{{ model.name }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-3">Alter Ego</div>
|
||||
<div class="col-xs-9 pull-left">{{ model.alterEgo }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-3">Power</div>
|
||||
<div class="col-xs-9 pull-left">{{ model.power }}</div>
|
||||
</div>
|
||||
<br>
|
||||
<button class="btn btn-default" (click)="submitted=false">Edit</button>
|
||||
</div>
|
||||
<!-- #enddocregion submitted -->
|
||||
</div>
|
||||
<!-- #enddocregion final -->
|
||||
|
||||
<!-- ==================================================== -->
|
||||
<div>
|
||||
<form>
|
||||
<!-- #docregion edit-div -->
|
||||
|
||||
<!-- ... all of the form ... -->
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<!-- #enddocregion edit-div -->
|
||||
|
||||
<!-- ==================================================== -->
|
||||
<hr>
|
||||
<style>
|
||||
.no-style .ng-valid {
|
||||
border-left: 1px solid #CCC
|
||||
}
|
||||
|
||||
.no-style .ng-invalid {
|
||||
border-left: 1px solid #CCC
|
||||
}
|
||||
</style>
|
||||
<div class="no-style" style="margin-left: 4px">
|
||||
<!-- #docregion start -->
|
||||
<div class="container">
|
||||
<h1>Hero Form</h1>
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
<input type="text" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="alterEgo">Alter Ego</label>
|
||||
<input type="text" class="form-control">
|
||||
</div>
|
||||
|
||||
<!-- #enddocregion start -->
|
||||
<!-- #docregion powers -->
|
||||
<div class="form-group">
|
||||
<label for="power">Hero Power</label>
|
||||
<select class="form-control" required>
|
||||
<option *ngFor="let p of powers" [value]="p">{{p}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- #enddocregion powers -->
|
||||
<!-- #docregion start -->
|
||||
<button type="submit" class="btn btn-default">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
<!-- #enddocregion start -->
|
||||
<!-- #enddocregion phase1-->
|
||||
|
||||
<!-- ==================================================== -->
|
||||
<hr>
|
||||
<!-- #docregion phase2-->
|
||||
<div class="container">
|
||||
<h1>Hero Form</h1>
|
||||
<form>
|
||||
<!-- #docregion ngModel-2-->
|
||||
{{diagnostic()}}
|
||||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
<input type="text" class="form-control" required
|
||||
[(ngModel)]="model.name" >
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="alterEgo">Alter Ego</label>
|
||||
<input type="text" class="form-control"
|
||||
[(ngModel)]="model.alterEgo">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="power">Hero Power</label>
|
||||
<select class="form-control" required
|
||||
[(ngModel)]="model.power" >
|
||||
<option *ngFor="let p of powers" [value]="p">{{p}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- #enddocregion ngModel-2-->
|
||||
<button type="submit" class="btn btn-default">Submit</button>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<!-- #enddocregion phase2-->
|
||||
|
||||
<!-- EXTRA MATERIAL FOR DOCUMENTATION -->
|
||||
<hr>
|
||||
<!-- #docregion ngModel-1-->
|
||||
<input type="text" class="form-control" required
|
||||
[(ngModel)]="model.name" >
|
||||
TODO: remove this: {{model.name}}
|
||||
<!-- #enddocregion ngModel-1-->
|
||||
<hr>
|
||||
<!-- #docregion ngModel-3-->
|
||||
<input type="text" class="form-control" required
|
||||
[ngModel]="model.name"
|
||||
(ngModelChange)="model.name = $event" >
|
||||
TODO: remove this: {{model.name}}
|
||||
<!-- #enddocregion ngModel-3-->
|
||||
<hr>
|
||||
<form>
|
||||
<!-- #docregion ngControl-1 -->
|
||||
<input type="text" class="form-control" required
|
||||
[(ngModel)]="model.name"
|
||||
ngControl="name" >
|
||||
<!-- #enddocregion ngControl-1 -->
|
||||
<hr>
|
||||
<!-- #docregion ngControl-2 -->
|
||||
<input type="text" class="form-control" required
|
||||
[(ngModel)]="model.name"
|
||||
ngControl="name" #spy >
|
||||
<br>TODO: remove this: {{spy.className}}
|
||||
<!-- #enddocregion ngControl-2 -->
|
||||
</form>
|
||||
|
||||
<div>
|
||||
<hr>
|
||||
Name via form.controls = {{showFormControls(heroForm)}}
|
||||
</div>
|
||||
|
||||
</div>
|
|
@ -0,0 +1,52 @@
|
|||
// #docplaster
|
||||
// #docregion
|
||||
// #docregion first, final
|
||||
(function(app) {
|
||||
app.HeroFormComponent = ng.core
|
||||
.Component({
|
||||
selector: 'hero-form',
|
||||
templateUrl: 'app/hero-form.component.html'
|
||||
})
|
||||
.Class({
|
||||
// #docregion submitted
|
||||
constructor: function() {
|
||||
// #enddocregion submitted
|
||||
this.powers = ['Really Smart', 'Super Flexible',
|
||||
'Super Hot', 'Weather Changer'
|
||||
];
|
||||
|
||||
this.model = new app.Hero(18, 'Dr IQ', this.powers[0],
|
||||
'Chuck Overstreet');
|
||||
|
||||
// #docregion submitted
|
||||
this.submitted = false;
|
||||
},
|
||||
onSubmit: function() {
|
||||
this.submitted = true;
|
||||
},
|
||||
// #enddocregion submitted
|
||||
|
||||
// #enddocregion final
|
||||
// TODO: Remove this when we're done
|
||||
diagnostic: function() {
|
||||
return JSON.stringify(this.model);
|
||||
},
|
||||
// #enddocregion first
|
||||
|
||||
|
||||
//////// DO NOT SHOW IN DOCS ////////
|
||||
|
||||
// Reveal in html:
|
||||
// AlterEgo via form.controls = {{showFormControls(hf)}}
|
||||
showFormControls: function(form) {
|
||||
return form.controls['alterEgo'] &&
|
||||
// #docregion form-controls
|
||||
form.controls['name'].value; // Dr. IQ
|
||||
// #enddocregion form-controls
|
||||
},
|
||||
/////////////////////////////
|
||||
|
||||
// #docregion first, final
|
||||
});
|
||||
// #enddocregion first, final
|
||||
})(window.app || (window.app = {}));
|
|
@ -0,0 +1,11 @@
|
|||
// #docregion
|
||||
(function(app) {
|
||||
app.Hero = Hero;
|
||||
|
||||
function Hero(id, name, power, alterEgo) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.power = power;
|
||||
this.alterEgo = alterEgo;
|
||||
}
|
||||
})(window.app || (window.app = {}));
|
|
@ -0,0 +1,6 @@
|
|||
// #docregion
|
||||
(function(app) {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
ng.platformBrowserDynamic.bootstrap(app.AppComponent);
|
||||
});
|
||||
})(window.app || (window.app = {}));
|
|
@ -0,0 +1,9 @@
|
|||
/* #docregion */
|
||||
.ng-valid[required] {
|
||||
border-left: 5px solid #42A948; /* green */
|
||||
}
|
||||
|
||||
.ng-invalid {
|
||||
border-left: 5px solid #a94442; /* red */
|
||||
}
|
||||
/* #enddocregion */
|
|
@ -0,0 +1,45 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- #docplaster -->
|
||||
<!-- #docregion -->
|
||||
<html>
|
||||
<head>
|
||||
<title>Hero Form</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<!-- #docregion bootstrap -->
|
||||
<link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.min.css">
|
||||
<!-- #enddocregion bootstrap -->
|
||||
<!-- #docregion styles -->
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="forms.css">
|
||||
<!-- #enddocregion styles -->
|
||||
|
||||
<!-- IE required polyfill -->
|
||||
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||
|
||||
<script src="node_modules/zone.js/dist/zone.js"></script>
|
||||
<script src="node_modules/reflect-metadata/Reflect.js"></script>
|
||||
|
||||
<script src="node_modules/rxjs/bundles/Rx.umd.js"></script>
|
||||
<script src="node_modules/@angular/core/bundles/core.umd.js"></script>
|
||||
<script src="node_modules/@angular/common/bundles/common.umd.js"></script>
|
||||
<script src="node_modules/@angular/compiler/bundles/compiler.umd.js"></script>
|
||||
<script src="node_modules/@angular/platform-browser/bundles/platform-browser.umd.js"></script>
|
||||
<script src="node_modules/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js"></script>
|
||||
|
||||
<!-- #docregion scripts-hero, scripts-hero-form -->
|
||||
<script src='app/hero.js'></script>
|
||||
<!-- #enddocregion scripts-hero -->
|
||||
<script src='app/hero-form.component.js'></script>
|
||||
<!-- #enddocregion scripts-hero-form -->
|
||||
<!-- #docregion scripts, scripts-hero, scripts-hero-form -->
|
||||
<script src='app/app.component.js'></script>
|
||||
<script src='app/main.js'></script>
|
||||
<!-- #enddocregion scripts, scripts-hero, scripts-hero-form -->
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<my-app>Loading...</my-app>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"description": "Forms",
|
||||
"files":["app/**/*.js", "**/*.html", "**/*.css"]
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
import { HeroFormComponent } from './hero-form.component';
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
template: '<hero-form></hero-form>',
|
||||
directives: [HeroFormComponent]
|
||||
})
|
||||
export class AppComponent { }
|
|
@ -0,0 +1,208 @@
|
|||
<!-- #docplaster -->
|
||||
<!-- #docregion final -->
|
||||
<div class="container">
|
||||
<!-- #docregion edit-div -->
|
||||
<div [hidden]="submitted">
|
||||
<h1>Hero Form</h1>
|
||||
<!-- #docregion ngSubmit -->
|
||||
<form *ngIf="active" (ngSubmit)="onSubmit()" #heroForm="ngForm">
|
||||
<!-- #enddocregion ngSubmit -->
|
||||
<!-- #enddocregion edit-div -->
|
||||
<div class="form-group">
|
||||
<!-- #docregion name-with-error-msg -->
|
||||
<label for="name">Name</label>
|
||||
<input type="text" class="form-control" required
|
||||
[(ngModel)]="model.name"
|
||||
ngControl="name" #name="ngForm" >
|
||||
<!-- #docregion hidden-error-msg -->
|
||||
<div [hidden]="name.valid || name.pristine" class="alert alert-danger">
|
||||
<!-- #enddocregion hidden-error-msg -->
|
||||
Name is required
|
||||
</div>
|
||||
<!-- #enddocregion name-with-error-msg -->
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="alterEgo">Alter Ego</label>
|
||||
<input type="text" class="form-control"
|
||||
[(ngModel)]="model.alterEgo"
|
||||
ngControl="alterEgo" >
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="power">Hero Power</label>
|
||||
<select class="form-control" required
|
||||
[(ngModel)]="model.power"
|
||||
ngControl="power" #power="ngForm" >
|
||||
<option *ngFor="let p of powers" [value]="p">{{p}}</option>
|
||||
</select>
|
||||
<div [hidden]="power.valid || power.pristine" class="alert alert-danger">
|
||||
Power is required
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- #docregion submit-button -->
|
||||
<button type="submit" class="btn btn-default" [disabled]="!heroForm.form.valid">Submit</button>
|
||||
<!-- #enddocregion submit-button -->
|
||||
|
||||
<!-- #docregion new-hero-button -->
|
||||
<button type="button" class="btn btn-default" (click)="newHero()">New Hero</button>
|
||||
<!-- #enddocregion new-hero-button -->
|
||||
|
||||
<!-- #enddocregion final -->
|
||||
<!-- NOT SHOWN IN DOCS -->
|
||||
<div>
|
||||
<hr>
|
||||
Name via form.controls = {{showFormControls(heroForm)}}
|
||||
</div>
|
||||
<!-- - -->
|
||||
<!-- #docregion final -->
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- #docregion submitted -->
|
||||
<div [hidden]="!submitted">
|
||||
<h2>You submitted the following:</h2>
|
||||
<div class="row">
|
||||
<div class="col-xs-3">Name</div>
|
||||
<div class="col-xs-9 pull-left">{{ model.name }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-3">Alter Ego</div>
|
||||
<div class="col-xs-9 pull-left">{{ model.alterEgo }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-3">Power</div>
|
||||
<div class="col-xs-9 pull-left">{{ model.power }}</div>
|
||||
</div>
|
||||
<br>
|
||||
<button class="btn btn-default" (click)="submitted=false">Edit</button>
|
||||
</div>
|
||||
<!-- #enddocregion submitted -->
|
||||
</div>
|
||||
<!-- #enddocregion final -->
|
||||
|
||||
<!-- ==================================================== -->
|
||||
<div>
|
||||
<form>
|
||||
<!-- #docregion edit-div -->
|
||||
|
||||
<!-- ... all of the form ... -->
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<!-- #enddocregion edit-div -->
|
||||
|
||||
<!-- ==================================================== -->
|
||||
<hr>
|
||||
<style>
|
||||
.no-style .ng-valid {
|
||||
border-left: 1px solid #CCC
|
||||
}
|
||||
|
||||
.no-style .ng-invalid {
|
||||
border-left: 1px solid #CCC
|
||||
}
|
||||
</style>
|
||||
<div class="no-style" style="margin-left: 4px">
|
||||
<!-- #docregion start -->
|
||||
<div class="container">
|
||||
<h1>Hero Form</h1>
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
<input type="text" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="alterEgo">Alter Ego</label>
|
||||
<input type="text" class="form-control">
|
||||
</div>
|
||||
|
||||
<!-- #enddocregion start -->
|
||||
<!-- #docregion powers -->
|
||||
<div class="form-group">
|
||||
<label for="power">Hero Power</label>
|
||||
<select class="form-control" required>
|
||||
<option *ngFor="let p of powers" [value]="p">{{p}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- #enddocregion powers -->
|
||||
<!-- #docregion start -->
|
||||
<button type="submit" class="btn btn-default">Submit</button>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<!-- #enddocregion start -->
|
||||
<!-- #enddocregion phase1-->
|
||||
|
||||
<!-- ==================================================== -->
|
||||
<hr>
|
||||
<!-- #docregion phase2-->
|
||||
<div class="container">
|
||||
<h1>Hero Form</h1>
|
||||
<form>
|
||||
<!-- #docregion ngModel-2-->
|
||||
{{diagnostic}}
|
||||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
<input type="text" class="form-control" required
|
||||
[(ngModel)]="model.name" >
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="alterEgo">Alter Ego</label>
|
||||
<input type="text" class="form-control"
|
||||
[(ngModel)]="model.alterEgo">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="power">Hero Power</label>
|
||||
<select class="form-control" required
|
||||
[(ngModel)]="model.power" >
|
||||
<option *ngFor="let p of powers" [value]="p">{{p}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- #enddocregion ngModel-2-->
|
||||
<button type="submit" class="btn btn-default">Submit</button>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<!-- #enddocregion phase2-->
|
||||
|
||||
<!-- EXTRA MATERIAL FOR DOCUMENTATION -->
|
||||
<hr>
|
||||
<!-- #docregion ngModel-1-->
|
||||
<input type="text" class="form-control" required
|
||||
[(ngModel)]="model.name" >
|
||||
TODO: remove this: {{model.name}}
|
||||
<!-- #enddocregion ngModel-1-->
|
||||
<hr>
|
||||
<!-- #docregion ngModel-3-->
|
||||
<input type="text" class="form-control" required
|
||||
[ngModel]="model.name"
|
||||
(ngModelChange)="model.name = $event" >
|
||||
TODO: remove this: {{model.name}}
|
||||
<!-- #enddocregion ngModel-3-->
|
||||
<hr>
|
||||
<!-- #docregion form-active -->
|
||||
<form *ngIf="active">
|
||||
<!-- #enddocregion form-active -->
|
||||
|
||||
<!-- #docregion ngControl-1 -->
|
||||
<input type="text" class="form-control" required
|
||||
[(ngModel)]="model.name"
|
||||
ngControl="name" >
|
||||
<!-- #enddocregion ngControl-1 -->
|
||||
<hr>
|
||||
<!-- #docregion ngControl-2 -->
|
||||
<input type="text" class="form-control" required
|
||||
[(ngModel)]="model.name"
|
||||
ngControl="name" #spy >
|
||||
<br>TODO: remove this: {{spy.className}}
|
||||
<!-- #enddocregion ngControl-2 -->
|
||||
</form>
|
||||
|
||||
</div>
|
|
@ -0,0 +1,66 @@
|
|||
// #docplaster
|
||||
// #docregion
|
||||
// #docregion first, final
|
||||
import { Component } from '@angular/core';
|
||||
import { NgForm } from '@angular/common';
|
||||
|
||||
import { Hero } from './hero';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-form',
|
||||
templateUrl: 'app/hero-form.component.html'
|
||||
})
|
||||
export class HeroFormComponent {
|
||||
|
||||
powers = ['Really Smart', 'Super Flexible',
|
||||
'Super Hot', 'Weather Changer'];
|
||||
|
||||
model = new Hero(18, 'Dr IQ', this.powers[0], 'Chuck Overstreet');
|
||||
|
||||
// #docregion submitted
|
||||
submitted = false;
|
||||
|
||||
onSubmit() { this.submitted = true; }
|
||||
// #enddocregion submitted
|
||||
|
||||
// #enddocregion final
|
||||
// TODO: Remove this when we're done
|
||||
get diagnostic() { return JSON.stringify(this.model); }
|
||||
// #enddocregion first
|
||||
|
||||
// #docregion final
|
||||
// Reset the form with a new hero AND restore 'pristine' class state
|
||||
// by toggling 'active' flag which causes the form
|
||||
// to be removed/re-added in a tick via NgIf
|
||||
// TODO: Workaround until NgForm has a reset method (#6822)
|
||||
// #docregion new-hero
|
||||
active = true;
|
||||
|
||||
// #docregion new-hero-v1
|
||||
newHero() {
|
||||
this.model = new Hero(42, '', '');
|
||||
// #enddocregion new-hero-v1
|
||||
this.active = false;
|
||||
setTimeout(() => this.active = true, 0);
|
||||
// #docregion new-hero-v1
|
||||
}
|
||||
// #enddocregion new-hero-v1
|
||||
// #enddocregion new-hero
|
||||
// #enddocregion final
|
||||
//////// NOT SHOWN IN DOCS ////////
|
||||
|
||||
// Reveal in html:
|
||||
// Name via form.controls = {{showFormControls(heroForm)}}
|
||||
showFormControls(form: NgForm) {
|
||||
|
||||
return form && form.controls['name'] &&
|
||||
// #docregion form-controls
|
||||
form.controls['name'].value; // Dr. IQ
|
||||
// #enddocregion form-controls
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
|
||||
// #docregion first, final
|
||||
}
|
||||
// #enddocregion first, final
|
|
@ -0,0 +1,11 @@
|
|||
// #docregion
|
||||
export class Hero {
|
||||
|
||||
constructor(
|
||||
public id: number,
|
||||
public name: string,
|
||||
public power: string,
|
||||
public alterEgo?: string
|
||||
) { }
|
||||
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
// #docregion
|
||||
import { bootstrap } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
bootstrap(AppComponent);
|
|
@ -0,0 +1,9 @@
|
|||
/* #docregion */
|
||||
.ng-valid[required] {
|
||||
border-left: 5px solid #42A948; /* green */
|
||||
}
|
||||
|
||||
.ng-invalid {
|
||||
border-left: 5px solid #a94442; /* red */
|
||||
}
|
||||
/* #enddocregion */
|
|
@ -0,0 +1,34 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- #docregion -->
|
||||
<html>
|
||||
<head>
|
||||
<title>Hero Form</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<!-- #docregion bootstrap -->
|
||||
<link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.min.css">
|
||||
<!-- #enddocregion bootstrap -->
|
||||
<!-- #docregion styles -->
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="forms.css">
|
||||
<!-- #enddocregion styles -->
|
||||
|
||||
<!-- Polyfill(s) for older browsers -->
|
||||
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||
|
||||
<script src="node_modules/zone.js/dist/zone.js"></script>
|
||||
<script src="node_modules/reflect-metadata/Reflect.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
|
||||
<script src="systemjs.config.js"></script>
|
||||
<script>
|
||||
System.import('app').catch(function(err){ console.error(err); });
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<my-app>Loading...</my-app>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"description": "Forms-Deprecated",
|
||||
"files":[
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js"
|
||||
]
|
||||
}
|
|
@ -33,7 +33,7 @@ describeIf(browser.appIsTs || browser.appIsJs, 'Forms Tests', function () {
|
|||
});
|
||||
|
||||
it('should hide form after submit', function () {
|
||||
let alterEgoEle = element.all(by.css('input[ngcontrol=alterEgo]')).get(0);
|
||||
let alterEgoEle = element.all(by.css('input[name=alterEgo]')).get(0);
|
||||
expect(alterEgoEle.isDisplayed()).toBe(true);
|
||||
let submitButtonEle = element.all(by.css('button[type=submit]')).get(0);
|
||||
submitButtonEle.click().then(function() {
|
||||
|
@ -44,7 +44,7 @@ describeIf(browser.appIsTs || browser.appIsJs, 'Forms Tests', function () {
|
|||
it('should reflect submitted data after submit', function () {
|
||||
let test = 'testing 1 2 3';
|
||||
let newValue: string;
|
||||
let alterEgoEle = element.all(by.css('input[ngcontrol=alterEgo]')).get(0);
|
||||
let alterEgoEle = element.all(by.css('input[name=alterEgo]')).get(0);
|
||||
alterEgoEle.getAttribute('value').then(function(value) {
|
||||
// alterEgoEle.sendKeys(test);
|
||||
sendKeys(alterEgoEle, test);
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<!-- #docregion name-with-error-msg -->
|
||||
<input type="text" class="form-control" required
|
||||
[(ngModel)]="model.name"
|
||||
ngControl="name" #name="ngForm" >
|
||||
name="name" #name="ngModel" >
|
||||
<div [hidden]="name.valid" class="alert alert-danger">
|
||||
Name is required
|
||||
</div>
|
||||
|
@ -24,14 +24,14 @@
|
|||
<label for="alterEgo">Alter Ego</label>
|
||||
<input type="text" class="form-control"
|
||||
[(ngModel)]="model.alterEgo"
|
||||
ngControl="alterEgo" >
|
||||
name="alterEgo" >
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="power">Hero Power</label>
|
||||
<select class="form-control" required
|
||||
[(ngModel)]="model.power"
|
||||
ngControl="power" #power="ngForm" >
|
||||
name="power" #power="ngModel" >
|
||||
<option *ngFor="let p of powers" [value]="p">{{p}}</option>
|
||||
</select>
|
||||
<div [hidden]="power.valid" class="alert alert-danger">
|
||||
|
@ -133,19 +133,19 @@
|
|||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
<input type="text" class="form-control" required
|
||||
[(ngModel)]="model.name" >
|
||||
[(ngModel)]="model.name" name="name" >
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="alterEgo">Alter Ego</label>
|
||||
<input type="text" class="form-control"
|
||||
[(ngModel)]="model.alterEgo">
|
||||
[(ngModel)]="model.alterEgo" name="alterEgo">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="power">Hero Power</label>
|
||||
<select class="form-control" required
|
||||
[(ngModel)]="model.power" >
|
||||
[(ngModel)]="model.power" name="power">
|
||||
<option *ngFor="let p of powers" [value]="p">{{p}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
@ -173,18 +173,18 @@
|
|||
<!-- #enddocregion ngModel-3-->
|
||||
<hr>
|
||||
<form>
|
||||
<!-- #docregion ngControl-1 -->
|
||||
<!-- #docregion ngModelName-1 -->
|
||||
<input type="text" class="form-control" required
|
||||
[(ngModel)]="model.name"
|
||||
ngControl="name" >
|
||||
<!-- #enddocregion ngControl-1 -->
|
||||
name="name" >
|
||||
<!-- #enddocregion ngModelName-1 -->
|
||||
<hr>
|
||||
<!-- #docregion ngControl-2 -->
|
||||
<!-- #docregion ngModelName-2 -->
|
||||
<input type="text" class="form-control" required
|
||||
[(ngModel)]="model.name"
|
||||
ngControl="name" #spy >
|
||||
name="name" #spy >
|
||||
<br>TODO: remove this: {{spy.className}}
|
||||
<!-- #enddocregion ngControl-2 -->
|
||||
<!-- #enddocregion ngModelName-2 -->
|
||||
</form>
|
||||
|
||||
<div>
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
// #docregion
|
||||
(function(app) {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
ng.platformBrowserDynamic.bootstrap(app.AppComponent);
|
||||
ng.platformBrowserDynamic.bootstrap(app.AppComponent,[
|
||||
ng.forms.disableDeprecatedForms(),
|
||||
ng.forms.provideForms()
|
||||
]);
|
||||
});
|
||||
})(window.app || (window.app = {}));
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
<script src="node_modules/@angular/core/bundles/core.umd.js"></script>
|
||||
<script src="node_modules/@angular/common/bundles/common.umd.js"></script>
|
||||
<script src="node_modules/@angular/compiler/bundles/compiler.umd.js"></script>
|
||||
<script src="node_modules/@angular/forms/bundles/forms.umd.js"></script>
|
||||
<script src="node_modules/@angular/platform-browser/bundles/platform-browser.umd.js"></script>
|
||||
<script src="node_modules/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js"></script>
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<label for="name">Name</label>
|
||||
<input type="text" class="form-control" required
|
||||
[(ngModel)]="model.name"
|
||||
ngControl="name" #name="ngForm" >
|
||||
name="name" #name="ngModel" >
|
||||
<!-- #docregion hidden-error-msg -->
|
||||
<div [hidden]="name.valid || name.pristine" class="alert alert-danger">
|
||||
<!-- #enddocregion hidden-error-msg -->
|
||||
|
@ -26,14 +26,14 @@
|
|||
<label for="alterEgo">Alter Ego</label>
|
||||
<input type="text" class="form-control"
|
||||
[(ngModel)]="model.alterEgo"
|
||||
ngControl="alterEgo" >
|
||||
name="alterEgo" >
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="power">Hero Power</label>
|
||||
<select class="form-control" required
|
||||
[(ngModel)]="model.power"
|
||||
ngControl="power" #power="ngForm" >
|
||||
name="power" #power="ngModel" >
|
||||
<option *ngFor="let p of powers" [value]="p">{{p}}</option>
|
||||
</select>
|
||||
<div [hidden]="power.valid || power.pristine" class="alert alert-danger">
|
||||
|
@ -148,19 +148,19 @@
|
|||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
<input type="text" class="form-control" required
|
||||
[(ngModel)]="model.name" >
|
||||
[(ngModel)]="model.name" name="name">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="alterEgo">Alter Ego</label>
|
||||
<input type="text" class="form-control"
|
||||
[(ngModel)]="model.alterEgo">
|
||||
[(ngModel)]="model.alterEgo" name="alterEgo">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="power">Hero Power</label>
|
||||
<select class="form-control" required
|
||||
[(ngModel)]="model.power" >
|
||||
[(ngModel)]="model.power" name="power">
|
||||
<option *ngFor="let p of powers" [value]="p">{{p}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
@ -176,7 +176,7 @@
|
|||
<hr>
|
||||
<!-- #docregion ngModel-1-->
|
||||
<input type="text" class="form-control" required
|
||||
[(ngModel)]="model.name" >
|
||||
[(ngModel)]="model.name" name="name">
|
||||
TODO: remove this: {{model.name}}
|
||||
<!-- #enddocregion ngModel-1-->
|
||||
<hr>
|
||||
|
@ -191,18 +191,18 @@
|
|||
<form *ngIf="active">
|
||||
<!-- #enddocregion form-active -->
|
||||
|
||||
<!-- #docregion ngControl-1 -->
|
||||
<!-- #docregion ngModelName-1 -->
|
||||
<input type="text" class="form-control" required
|
||||
[(ngModel)]="model.name"
|
||||
ngControl="name" >
|
||||
<!-- #enddocregion ngControl-1 -->
|
||||
name="name" >
|
||||
<!-- #enddocregion ngModelName-1 -->
|
||||
<hr>
|
||||
<!-- #docregion ngControl-2 -->
|
||||
<!-- #docregion ngModelName-2 -->
|
||||
<input type="text" class="form-control" required
|
||||
[(ngModel)]="model.name"
|
||||
ngControl="name" #spy >
|
||||
name="name" #spy >
|
||||
<br>TODO: remove this: {{spy.className}}
|
||||
<!-- #enddocregion ngControl-2 -->
|
||||
<!-- #enddocregion ngModelName-2 -->
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
// #docregion
|
||||
import { bootstrap } from '@angular/platform-browser-dynamic';
|
||||
import { disableDeprecatedForms, provideForms } from '@angular/forms';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
bootstrap(AppComponent);
|
||||
bootstrap(AppComponent, [
|
||||
disableDeprecatedForms(),
|
||||
provideForms()
|
||||
])
|
||||
.catch((err: any) => console.error(err));
|
||||
|
|
|
@ -25,15 +25,16 @@
|
|||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@angular/common": "2.0.0-rc.2",
|
||||
"@angular/compiler": "2.0.0-rc.2",
|
||||
"@angular/core": "2.0.0-rc.2",
|
||||
"@angular/http": "2.0.0-rc.2",
|
||||
"@angular/platform-browser": "2.0.0-rc.2",
|
||||
"@angular/platform-browser-dynamic": "2.0.0-rc.2",
|
||||
"@angular/router": "2.0.0-rc.2",
|
||||
"@angular/common": "2.0.0-rc.3",
|
||||
"@angular/compiler": "2.0.0-rc.3",
|
||||
"@angular/core": "2.0.0-rc.3",
|
||||
"@angular/forms": "0.1.1",
|
||||
"@angular/http": "2.0.0-rc.3",
|
||||
"@angular/platform-browser": "2.0.0-rc.3",
|
||||
"@angular/platform-browser-dynamic": "2.0.0-rc.3",
|
||||
"@angular/router": "3.0.0-alpha.7",
|
||||
"@angular/router-deprecated": "2.0.0-rc.2",
|
||||
"@angular/upgrade": "2.0.0-rc.2",
|
||||
"@angular/upgrade": "2.0.0-rc.3",
|
||||
"angular2-in-memory-web-api": "0.0.12",
|
||||
"bootstrap": "^3.3.6",
|
||||
"core-js": "^2.4.0",
|
||||
|
|
|
@ -7,15 +7,16 @@
|
|||
},
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@angular/common": "2.0.0-rc.2",
|
||||
"@angular/compiler": "2.0.0-rc.2",
|
||||
"@angular/core": "2.0.0-rc.2",
|
||||
"@angular/http": "2.0.0-rc.2",
|
||||
"@angular/platform-browser": "2.0.0-rc.2",
|
||||
"@angular/platform-browser-dynamic": "2.0.0-rc.2",
|
||||
"@angular/router": "2.0.0-rc.2",
|
||||
"@angular/common": "2.0.0-rc.3",
|
||||
"@angular/compiler": "2.0.0-rc.3",
|
||||
"@angular/core": "2.0.0-rc.3",
|
||||
"@angular/forms": "0.1.1",
|
||||
"@angular/http": "2.0.0-rc.3",
|
||||
"@angular/platform-browser": "2.0.0-rc.3",
|
||||
"@angular/platform-browser-dynamic": "2.0.0-rc.3",
|
||||
"@angular/router": "3.0.0-alpha.7",
|
||||
"@angular/router-deprecated": "2.0.0-rc.2",
|
||||
"@angular/upgrade": "2.0.0-rc.2",
|
||||
"@angular/upgrade": "2.0.0-rc.3",
|
||||
|
||||
"core-js": "^2.4.0",
|
||||
"reflect-metadata": "0.1.3",
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
/* #docregion */
|
||||
h1 {
|
||||
color: #369;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-size: 250%;
|
||||
}
|
||||
body {
|
||||
margin: 2em;
|
||||
}
|
||||
|
||||
/*
|
||||
* See https://github.com/angular/angular.io/blob/master/public/docs/_examples/styles.css
|
||||
* for the full set of master styles used by the documentation samples
|
||||
*/
|
|
@ -11,15 +11,16 @@
|
|||
},
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@angular/common": "2.0.0-rc.2",
|
||||
"@angular/compiler": "2.0.0-rc.2",
|
||||
"@angular/core": "2.0.0-rc.2",
|
||||
"@angular/http": "2.0.0-rc.2",
|
||||
"@angular/platform-browser": "2.0.0-rc.2",
|
||||
"@angular/platform-browser-dynamic": "2.0.0-rc.2",
|
||||
"@angular/router": "2.0.0-rc.2",
|
||||
"@angular/common": "2.0.0-rc.3",
|
||||
"@angular/compiler": "2.0.0-rc.3",
|
||||
"@angular/core": "2.0.0-rc.3",
|
||||
"@angular/forms": "0.1.1",
|
||||
"@angular/http": "2.0.0-rc.3",
|
||||
"@angular/platform-browser": "2.0.0-rc.3",
|
||||
"@angular/platform-browser-dynamic": "2.0.0-rc.3",
|
||||
"@angular/router": "3.0.0-alpha.7",
|
||||
"@angular/router-deprecated": "2.0.0-rc.2",
|
||||
"@angular/upgrade": "2.0.0-rc.2",
|
||||
"@angular/upgrade": "2.0.0-rc.3",
|
||||
|
||||
"systemjs": "0.19.27",
|
||||
"core-js": "^2.4.0",
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
'common',
|
||||
'compiler',
|
||||
'core',
|
||||
'forms',
|
||||
'http',
|
||||
'platform-browser',
|
||||
'platform-browser-dynamic',
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"globalDependencies": {
|
||||
"core-js": "registry:dt/core-js#0.0.0+20160317120654",
|
||||
"jasmine": "registry:dt/jasmine#2.2.0+20160505161446",
|
||||
"node": "registry:dt/node#6.0.0+20160613154055"
|
||||
"core-js": "registry:dt/core-js#0.0.0+20160602141332",
|
||||
"jasmine": "registry:dt/jasmine#2.2.0+20160621224255",
|
||||
"node": "registry:dt/node#6.0.0+20160621231320"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,14 +24,18 @@ describe('Router', function () {
|
|||
heroDetail: element(by.css('my-app > undefined > div')),
|
||||
heroDetailTitle: element(by.css('my-app > undefined > div > h3')),
|
||||
|
||||
adminHref: hrefEles.get(2),
|
||||
loginHref: hrefEles.get(3)
|
||||
};
|
||||
}
|
||||
|
||||
it('should be able to see the start screen', function () {
|
||||
let page = getPageStruct();
|
||||
expect(page.hrefs.count()).toEqual(2, 'should be two dashboard choices');
|
||||
expect(page.hrefs.count()).toEqual(4, 'should be two dashboard choices');
|
||||
expect(page.crisisHref.getText()).toEqual('Crisis Center');
|
||||
expect(page.heroesHref.getText()).toEqual('Heroes');
|
||||
expect(page.adminHref.getText()).toEqual('Crisis Admin');
|
||||
expect(page.loginHref.getText()).toEqual('Login');
|
||||
});
|
||||
|
||||
it('should be able to see crises center items', function () {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
import { Router, ROUTER_DIRECTIVES } from '@angular/router';
|
||||
import { ROUTER_DIRECTIVES } from '@angular/router';
|
||||
|
||||
// #enddocregion
|
||||
/*
|
||||
|
|
|
@ -16,11 +16,11 @@ import { HeroDetailComponent } from './heroes/hero-detail.component';
|
|||
// #docregion route-config
|
||||
export const routes: RouterConfig = [
|
||||
// #docregion route-defs
|
||||
{ path: '/crisis-center', component: CrisisCenterComponent },
|
||||
{ path: '/heroes', component: HeroListComponent },
|
||||
{ path: 'crisis-center', component: CrisisCenterComponent },
|
||||
{ path: 'heroes', component: HeroListComponent },
|
||||
// #enddocregion route-defs
|
||||
// #docregion hero-detail-route
|
||||
{ path: '/hero/:id', component: HeroDetailComponent }
|
||||
{ path: 'hero/:id', component: HeroDetailComponent }
|
||||
// #enddocregion hero-detail-route
|
||||
];
|
||||
|
||||
|
|
|
@ -9,8 +9,8 @@ import { HeroListComponent } from './hero-list.component';
|
|||
|
||||
// #docregion route-config
|
||||
export const routes: RouterConfig = [
|
||||
{ path: '/crisis-center', component: CrisisListComponent },
|
||||
{ path: '/heroes', component: HeroListComponent }
|
||||
{ path: 'crisis-center', component: CrisisListComponent },
|
||||
{ path: 'heroes', component: HeroListComponent }
|
||||
];
|
||||
|
||||
export const APP_ROUTER_PROVIDERS = [
|
||||
|
|
|
@ -7,7 +7,7 @@ import { HeroesRoutes } from './heroes/heroes.routes';
|
|||
|
||||
export const routes = [
|
||||
...HeroesRoutes,
|
||||
{ path: '/crisis-center', component: CrisisListComponent }
|
||||
{ path: 'crisis-center', component: CrisisListComponent }
|
||||
];
|
||||
|
||||
export const APP_ROUTER_PROVIDERS = [
|
||||
|
|
|
@ -7,11 +7,11 @@ import { CrisisCenterComponent } from './crisis-center.component';
|
|||
// #docregion routes
|
||||
export const CrisisCenterRoutes: RouterConfig = [
|
||||
{
|
||||
path: '/crisis-center',
|
||||
path: 'crisis-center',
|
||||
component: CrisisCenterComponent,
|
||||
children: [
|
||||
{ path: '/', component: CrisisListComponent },
|
||||
{ path: '/:id', component: CrisisDetailComponent }
|
||||
{ path: ':id', component: CrisisDetailComponent },
|
||||
{ path: '', component: CrisisListComponent }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
|
|
@ -6,17 +6,20 @@ import { CrisisCenterComponent } from './crisis-center.component';
|
|||
|
||||
// #docregion routes
|
||||
export const CrisisCenterRoutes: RouterConfig = [
|
||||
// #docregion redirect
|
||||
{
|
||||
path: '/crisis-center',
|
||||
path: '',
|
||||
redirectTo: '/crisis-center',
|
||||
terminal: true
|
||||
},
|
||||
// #enddocregion redirect
|
||||
{
|
||||
path: 'crisis-center',
|
||||
component: CrisisCenterComponent,
|
||||
index: true,
|
||||
children: [
|
||||
{ path: '/:id', component: CrisisDetailComponent },
|
||||
{ path: '/', component: CrisisListComponent,
|
||||
index: true
|
||||
}
|
||||
{ path: ':id', component: CrisisDetailComponent },
|
||||
{ path: '', component: CrisisListComponent }
|
||||
]
|
||||
}
|
||||
];
|
||||
// #enddocregion routes
|
||||
|
||||
|
|
|
@ -10,25 +10,28 @@ import { CanDeactivateGuard } from '../interfaces';
|
|||
|
||||
export const CrisisCenterRoutes: RouterConfig = [
|
||||
{
|
||||
path: '/crisis-center',
|
||||
path: '',
|
||||
redirectTo: '/crisis-center',
|
||||
terminal: true
|
||||
},
|
||||
{
|
||||
path: 'crisis-center',
|
||||
component: CrisisCenterComponent,
|
||||
index: true,
|
||||
children: [
|
||||
// #docregion admin-route-no-guard
|
||||
{
|
||||
path: '/admin',
|
||||
path: 'admin',
|
||||
component: CrisisAdminComponent
|
||||
},
|
||||
// #enddocregion admin-route-no-guard
|
||||
{
|
||||
path: '/:id',
|
||||
path: ':id',
|
||||
component: CrisisDetailComponent,
|
||||
canDeactivate: [CanDeactivateGuard]
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
component: CrisisListComponent,
|
||||
index: true
|
||||
path: '',
|
||||
component: CrisisListComponent
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -40,7 +43,7 @@ export const CrisisCenterRoutes: RouterConfig = [
|
|||
import { AuthGuard } from '../auth.guard';
|
||||
|
||||
{
|
||||
path: '/admin',
|
||||
path: 'admin',
|
||||
component: CrisisAdminComponent,
|
||||
canActivate: [AuthGuard]
|
||||
}
|
||||
|
|
|
@ -11,25 +11,28 @@ import { AuthGuard } from '../auth.guard';
|
|||
|
||||
export const CrisisCenterRoutes: RouterConfig = [
|
||||
{
|
||||
path: '/crisis-center',
|
||||
path: '',
|
||||
redirectTo: '/crisis-center',
|
||||
terminal: true
|
||||
},
|
||||
{
|
||||
path: 'crisis-center',
|
||||
component: CrisisCenterComponent,
|
||||
index: true,
|
||||
children: [
|
||||
{
|
||||
path: '/admin',
|
||||
path: 'admin',
|
||||
component: CrisisAdminComponent,
|
||||
canActivate: [AuthGuard]
|
||||
},
|
||||
{
|
||||
path: '/:id',
|
||||
path: ':id',
|
||||
component: CrisisDetailComponent,
|
||||
canDeactivate: [CanDeactivateGuard]
|
||||
},
|
||||
// #docregion default-route
|
||||
{
|
||||
path: '/',
|
||||
component: CrisisListComponent,
|
||||
index: true
|
||||
path: '',
|
||||
component: CrisisListComponent
|
||||
}
|
||||
// #enddocregion default-route
|
||||
]
|
||||
|
@ -42,7 +45,7 @@ export const CrisisCenterRoutes: RouterConfig = [
|
|||
import { AuthGuard } from '../auth.guard';
|
||||
|
||||
{
|
||||
path: '/admin',
|
||||
path: 'admin',
|
||||
component: CrisisAdminComponent,
|
||||
canActivate: [AuthGuard]
|
||||
}
|
||||
|
|
|
@ -10,25 +10,29 @@ import { AuthGuard } from '../auth.guard';
|
|||
|
||||
export const CrisisCenterRoutes: RouterConfig = [
|
||||
{
|
||||
path: '/crisis-center',
|
||||
path: '',
|
||||
redirectTo: '/crisis-center',
|
||||
terminal: true
|
||||
},
|
||||
{
|
||||
path: 'crisis-center',
|
||||
component: CrisisCenterComponent,
|
||||
index: true,
|
||||
children: [
|
||||
// #docregion admin-route
|
||||
{
|
||||
path: '/admin',
|
||||
path: 'admin',
|
||||
component: CrisisAdminComponent,
|
||||
canActivate: [AuthGuard]
|
||||
},
|
||||
// #enddocregion admin-route
|
||||
{
|
||||
path: '/:id',
|
||||
path: ':id',
|
||||
component: CrisisDetailComponent,
|
||||
canDeactivate: [CanDeactivateGuard]
|
||||
},
|
||||
{ path: '/',
|
||||
component: CrisisListComponent,
|
||||
index: true
|
||||
{
|
||||
path: '',
|
||||
component: CrisisListComponent
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ const CRISES = [
|
|||
let crisesPromise = Promise.resolve(CRISES);
|
||||
|
||||
// #docregion
|
||||
import {Injectable} from '@angular/core';
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable()
|
||||
export class CrisisService {
|
||||
|
|
|
@ -56,7 +56,7 @@ export class HeroDetailComponent implements OnInit, OnDestroy {
|
|||
let heroId = this.hero ? this.hero.id : null;
|
||||
// Pass along the hero id if available
|
||||
// so that the HeroList component can select that hero.
|
||||
this.router.navigate(['/heroes'], { queryParams: { id: `${heroId}`, foo: 'foo' } });
|
||||
this.router.navigate(['/heroes'], { queryParams: { id: heroId, foo: 'foo' } });
|
||||
}
|
||||
// #enddocregion gotoHeroes-navigate
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@ import { HeroListComponent } from './hero-list.component';
|
|||
import { HeroDetailComponent } from './hero-detail.component';
|
||||
|
||||
export const HeroesRoutes: RouterConfig = [
|
||||
{ path: '/heroes', component: HeroListComponent },
|
||||
{ path: 'heroes', component: HeroListComponent },
|
||||
// #docregion hero-detail-route
|
||||
{ path: '/hero/:id', component: HeroDetailComponent }
|
||||
{ path: 'hero/:id', component: HeroDetailComponent }
|
||||
// #enddocregion hero-detail-route
|
||||
];
|
||||
// #enddocregion
|
||||
|
|
|
@ -4,7 +4,6 @@ import { Router } from '@angular/router';
|
|||
import { AuthService } from './auth.service';
|
||||
|
||||
@Component({
|
||||
selector: 'login',
|
||||
template: `
|
||||
<h2>LOGIN</h2>
|
||||
<p>{{message}}</p>
|
||||
|
@ -25,7 +24,7 @@ export class LoginComponent {
|
|||
}
|
||||
|
||||
login() {
|
||||
this.message = "Trying to log in ...";
|
||||
this.message = 'Trying to log in ...';
|
||||
|
||||
this.authService.login().subscribe(() => {
|
||||
this.setMessage();
|
||||
|
|
|
@ -4,7 +4,7 @@ import { AuthService } from './auth.service';
|
|||
import { LoginComponent } from './login.component';
|
||||
|
||||
export const LoginRoutes = [
|
||||
{ path: '/login', component: LoginComponent }
|
||||
{ path: 'login', component: LoginComponent }
|
||||
];
|
||||
|
||||
export const AUTH_PROVIDERS = [AuthGuard, AuthService];
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/// <reference path="../_protractor/e2e.d.ts" />
|
||||
'use strict';
|
||||
describe('Security E2E Tests', () => {
|
||||
beforeAll(function() { browser.get(''); });
|
||||
|
||||
it('sanitizes innerHTML', () => {
|
||||
let interpolated = element(By.className('e2e-inner-html-interpolated'));
|
||||
expect(interpolated.getText())
|
||||
.toContain('Template <script>alert("0wned")</script> <b>Syntax</b>');
|
||||
let bound = element(By.className('e2e-inner-html-bound'));
|
||||
expect(bound.getText()).toContain('Template alert("0wned") Syntax');
|
||||
let bold = element(By.css('.e2e-inner-html-bound b'));
|
||||
expect(bold.getText()).toContain('Syntax');
|
||||
});
|
||||
|
||||
it('binds trusted URLs', () => {
|
||||
let dangerousUrl = element(By.className('e2e-dangerous-url'));
|
||||
expect(dangerousUrl.getAttribute('href')).toMatch(/^javascript:alert/);
|
||||
});
|
||||
|
||||
it('binds trusted resource URLs', () => {
|
||||
let iframe = element(By.className('e2e-iframe'));
|
||||
expect(iframe.getAttribute('src')).toMatch(/^https:\/\/www.youtube.com\//);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { BypassSecurityComponent } from './bypass-security.component';
|
||||
import { InnerHtmlBindingComponent } from './inner-html-binding.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
template: `
|
||||
<h1>Security</h1>
|
||||
<inner-html-binding></inner-html-binding>
|
||||
<bypass-security></bypass-security>
|
||||
`,
|
||||
directives: [
|
||||
BypassSecurityComponent,
|
||||
InnerHtmlBindingComponent,
|
||||
],
|
||||
})
|
||||
export class AppComponent {
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<!--#docregion -->
|
||||
<h3>Bypass Security Component</h3>
|
||||
|
||||
<!--#docregion dangerous-url -->
|
||||
<h4>A dangerous URL:</h4>
|
||||
<p><a class="e2e-dangerous-url" [href]="dangerousUrl">Click me.</a></p>
|
||||
<!--#enddocregion dangerous-url -->
|
||||
|
||||
<!--#docregion iframe-videoid -->
|
||||
<h4>Resource URL:</h4>
|
||||
<p><label>Showing: <input (input)="updateVideoUrl($event.target.value)"></label></p>
|
||||
<iframe class="e2e-iframe" width="640" height="390" [src]="videoUrl"></iframe>
|
||||
<!--#enddocregion iframe-videoid -->
|
||||
|
||||
<!--#enddocregion -->
|
|
@ -0,0 +1,33 @@
|
|||
// #docplaster
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
import { DomSanitizationService, SafeResourceUrl, SafeUrl } from '@angular/platform-browser';
|
||||
|
||||
@Component({
|
||||
selector: 'bypass-security',
|
||||
templateUrl: 'app/bypass-security.component.html',
|
||||
})
|
||||
export class BypassSecurityComponent {
|
||||
dangerousUrl: SafeUrl;
|
||||
videoUrl: SafeResourceUrl;
|
||||
|
||||
// #docregion trust-url
|
||||
constructor(private sanitizer: DomSanitizationService) {
|
||||
// javascript: URLs are dangerous if attacker controlled. Angular sanitizes them in data
|
||||
// binding, but we can explicitly tell Angular to trust this value:
|
||||
this.dangerousUrl = sanitizer.bypassSecurityTrustUrl('javascript:alert("Hi there")');
|
||||
// #enddocregion trust-url
|
||||
this.updateVideoUrl('PUBnlbjZFAI');
|
||||
}
|
||||
|
||||
// #docregion trust-video-url
|
||||
updateVideoUrl(id: string) {
|
||||
// Appending an ID to a YouTube URL is safe.
|
||||
// Always make sure to construct SafeValue objects as close as possible to the input data, so
|
||||
// that it's easier to check if the value is safe.
|
||||
this.videoUrl =
|
||||
this.sanitizer.bypassSecurityTrustResourceUrl('https://www.youtube.com/embed/' + id);
|
||||
}
|
||||
// #enddocregion trust-video-url
|
||||
}
|
||||
// #enddocregion
|
|
@ -0,0 +1,6 @@
|
|||
<!-- #docregion -->
|
||||
<h3>Binding innerHTML</h3>
|
||||
<p>Bound value:</p>
|
||||
<p class="e2e-inner-html-interpolated">{{htmlSnippet}}</p>
|
||||
<p>Result of binding to innerHTML:</p>
|
||||
<p class="e2e-inner-html-bound" [innerHTML]="htmlSnippet"></p>
|
|
@ -0,0 +1,14 @@
|
|||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'inner-html-binding',
|
||||
templateUrl: 'inner-html-binding.component.html',
|
||||
})
|
||||
// #docregion inner-html-controller
|
||||
export class InnerHtmlBindingComponent {
|
||||
// E.g. a user/attacker controlled value from a URL.
|
||||
htmlSnippet = 'Template <script>alert("0wned")</script> <b>Syntax</b>';
|
||||
}
|
||||
// #enddocregion inner-html-controller
|
|
@ -0,0 +1,8 @@
|
|||
// #docregion
|
||||
import { bootstrap } from '@angular/platform-browser-dynamic';
|
||||
|
||||
// #docregion import
|
||||
import { AppComponent } from './app.component';
|
||||
// #enddocregion import
|
||||
|
||||
bootstrap(AppComponent);
|
|
@ -0,0 +1,26 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- #docregion -->
|
||||
<html>
|
||||
<head>
|
||||
<title>Angular Content Security</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
||||
<!-- Polyfill(s) for older browsers -->
|
||||
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||
|
||||
<script src="node_modules/zone.js/dist/zone.js"></script>
|
||||
<script src="node_modules/reflect-metadata/Reflect.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
|
||||
<script src="systemjs.config.js"></script>
|
||||
<script>
|
||||
System.import('app').catch(function(err){ console.error(err); });
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<app-root>Loading...</app-root>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"description": "Content Security",
|
||||
"files": [
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js"
|
||||
],
|
||||
"tags": ["security"]
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'toh-dashboard',
|
||||
templateUrl: 'app/dashboard/dashboard.component.html'
|
||||
})
|
||||
export class DashboardComponent implements OnInit {
|
||||
constructor() { }
|
||||
|
||||
ngOnInit() { }
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
export * from './dashboard.component';
|
|
@ -1,11 +0,0 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'toh-heroes',
|
||||
templateUrl: 'app/heroes/heroes.component.html'
|
||||
})
|
||||
export class HeroesComponent implements OnInit {
|
||||
constructor() { }
|
||||
|
||||
ngOnInit() { }
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
export * from './shared';
|
||||
export * from './heroes.component.ts';
|
|
@ -1,8 +0,0 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable()
|
||||
export class HeroService {
|
||||
|
||||
constructor() { }
|
||||
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
export * from './hero.service';
|
|
@ -1,23 +0,0 @@
|
|||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
import { Routes, ROUTER_DIRECTIVES, ROUTER_PROVIDERS } from '@angular/router';
|
||||
|
||||
import { NavComponent } from './shared';
|
||||
import { DashboardComponent } from './+dashboard';
|
||||
import { HeroesComponent, HeroService } from './+heroes';
|
||||
|
||||
@Component({
|
||||
selector: 'toh-app',
|
||||
templateUrl: 'app/app.component.html',
|
||||
styleUrls: ['app/app.component.css'],
|
||||
directives: [ROUTER_DIRECTIVES, NavComponent],
|
||||
providers: [
|
||||
ROUTER_PROVIDERS,
|
||||
HeroService
|
||||
]
|
||||
})
|
||||
@Routes([
|
||||
{ path: '/dashboard', component: DashboardComponent }, // , useAsDefault: true}, // coming soon
|
||||
{ path: '/heroes/...', component: HeroesComponent },
|
||||
])
|
||||
export class AppComponent {}
|
|
@ -1,4 +0,0 @@
|
|||
export * from './+dashboard';
|
||||
export * from './+heroes';
|
||||
export * from './shared';
|
||||
export * from './app.component';
|
|
@ -1 +0,0 @@
|
|||
export * from './nav';
|
|
@ -1 +0,0 @@
|
|||
export * from './nav.component';
|
|
@ -1,12 +0,0 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'toh-nav',
|
||||
templateUrl: 'app/shared/nav/nav.component.html'
|
||||
})
|
||||
export class NavComponent implements OnInit {
|
||||
constructor() { }
|
||||
|
||||
ngOnInit() { }
|
||||
|
||||
}
|
|
@ -24,6 +24,7 @@
|
|||
'common',
|
||||
'compiler',
|
||||
'core',
|
||||
'forms',
|
||||
'http',
|
||||
'platform-browser',
|
||||
'platform-browser-dynamic',
|
||||
|
|
|
@ -5,8 +5,9 @@
|
|||
*/
|
||||
(function(global) {
|
||||
|
||||
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.3'; // lock router version
|
||||
var ngVer = '@2.0.0-rc.3'; // 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.1'; // 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',
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"globalDependencies": {
|
||||
"core-js": "registry:dt/core-js#0.0.0+20160317120654",
|
||||
"jasmine": "registry:dt/jasmine#2.2.0+20160505161446",
|
||||
"node": "registry:dt/node#6.0.0+20160613154055"
|
||||
"core-js": "registry:dt/core-js#0.0.0+20160602141332",
|
||||
"jasmine": "registry:dt/jasmine#2.2.0+20160621224255",
|
||||
"node": "registry:dt/node#6.0.0+20160621231320"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
'common',
|
||||
'compiler',
|
||||
'core',
|
||||
'forms',
|
||||
'http',
|
||||
'platform-browser',
|
||||
'platform-browser-dynamic',
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
'common',
|
||||
'compiler',
|
||||
'core',
|
||||
'forms',
|
||||
'http',
|
||||
'platform-browser',
|
||||
'platform-browser-dynamic',
|
||||
|
|
|
@ -10,12 +10,13 @@
|
|||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@angular/common": "2.0.0-rc.2",
|
||||
"@angular/compiler": "2.0.0-rc.2",
|
||||
"@angular/core": "2.0.0-rc.2",
|
||||
"@angular/http": "2.0.0-rc.2",
|
||||
"@angular/platform-browser": "2.0.0-rc.2",
|
||||
"@angular/platform-browser-dynamic": "2.0.0-rc.2",
|
||||
"@angular/common": "2.0.0-rc.3",
|
||||
"@angular/compiler": "2.0.0-rc.3",
|
||||
"@angular/core": "2.0.0-rc.3",
|
||||
"@angular/forms": "0.1.1",
|
||||
"@angular/http": "2.0.0-rc.3",
|
||||
"@angular/platform-browser": "2.0.0-rc.3",
|
||||
"@angular/platform-browser-dynamic": "2.0.0-rc.3",
|
||||
"@angular/router-deprecated": "2.0.0-rc.2",
|
||||
"core-js": "^2.4.0",
|
||||
"reflect-metadata": "0.1.2",
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue