docs(user input): sample shuffle, revisions. alpha.53

closes #492
This commit is contained in:
Ward Bell 2015-12-13 15:15:46 -08:00
parent dc1c054927
commit 31051d29f0
16 changed files with 338 additions and 230 deletions

View File

@ -0,0 +1,27 @@
<p>
<click-me></click-me>
</p>
<p>
<click-me2></click-me2>
</p>
<h4>Give me some keys!</h4>
<div><key-up1></key-up1></div>
<h4>keyup loop-back component</h4>
<div><loop-back></loop-back></div>
<br><br>
<h4>Give me some more keys!</h4>
<div><key-up2></key-up2></div>
<h4>Type away! Press [enter] when done.</h4>
<div><key-up3></key-up3></div>
<h4>Type away! Press [enter] or click elsewhere when done.</h4>
<div><key-up4></key-up4></div>
<h4>Little Tour of Heroes</h4>
<p><i>Add a new hero</i></p>
<div><little-tour></little-tour></div>

View File

@ -0,0 +1,26 @@
// #docregion
import {Component} from 'angular2/core';
import {ClickMeComponent} from './click-me.component';
import {ClickMeComponent2} from './click-me2.component';
import {LoopbackComponent} from './loop-back.component';
import {KeyUpComponent_v1,
KeyUpComponent_v2,
KeyUpComponent_v3,
KeyUpComponent_v4} from './keyup.components';
import {LittleTourComponent} from './little-tour.component';
@Component({
selector: 'my-app',
templateUrl: 'app/app.component.html',
directives: [
ClickMeComponent, ClickMeComponent2,
LoopbackComponent,
KeyUpComponent_v1, KeyUpComponent_v2, KeyUpComponent_v3, KeyUpComponent_v4,
LittleTourComponent
]
})
export class AppComponent { }

View File

@ -0,0 +1,4 @@
import {bootstrap} from 'angular2/platform/browser';
import {AppComponent} from './app.component';
bootstrap(AppComponent);

View File

@ -0,0 +1,24 @@
/* FOR DOCS ... MUST MATCH ClickMeComponent template
// #docregion click-me-button
<button (click)="onClickMe()">Click me!</button>
// #enddocregion click-me-button
*/
// #docregion
import {Component} from 'angular2/core';
// #docregion click-me-component
@Component({
selector: 'click-me',
template: `
<button (click)="onClickMe()">Click me!</button>
{{clickMessage}}`
})
export class ClickMeComponent {
clickMessage = '';
onClickMe(){
this.clickMessage ='You are my hero!';
}
}
// #enddocregion click-me-component

View File

@ -0,0 +1,18 @@
// #docregion
import {Component} from 'angular2/core';
@Component({
selector: 'click-me2',
template: `
<button (click)="onClickMe2($event)">No! .. Click me!</button>
{{clickMessage}}`
})
export class ClickMeComponent2 {
clickMessage = '';
clicks = 1;
onClickMe2(event:any){
let evtMsg = event ? ' Event target is '+ event.target.tagName : '';
this.clickMessage = (`Click #${this.clicks++}. ${evtMsg}`)
}
}

View File

@ -0,0 +1,88 @@
// #docplaster
// #docregion
import {Component} from 'angular2/core';
// #docregion key-up-component-1
@Component({
selector: 'key-up1',
// #docregion key-up-component-1-template
template: `
<input (keyup)="onKey($event)">
<p>{{values}}</p>
`
// #enddocregion key-up-component-1-template
})
// #docregion key-up-component-1-class, key-up-component-1-class-no-type
export class KeyUpComponent_v1 {
values='';
// #enddocregion key-up-component-1-class, key-up-component-1-class-no-type
/*
// #docregion key-up-component-1-class-no-type
// without strong typing
onKey(event:any) {
this.values += event.target.value + ' | ';
}
// #enddocregion key-up-component-1-class-no-type
*/
// #docregion key-up-component-1-class
// with strong typing
onKey(event:KeyboardEvent) {
this.values += (<HTMLInputElement>event.target).value + ' | ';
}
// #docregion key-up-component-1-class-no-type
}
// #enddocregion key-up-component-1,key-up-component-1-class, key-up-component-1-class-no-type
//////////////////////////////////////////
// #docregion key-up-component-2
@Component({
selector: 'key-up2',
template: `
<input #box (keyup)="onKey(box.value)">
<p>{{values}}</p>
`
})
export class KeyUpComponent_v2 {
values='';
onKey(value:string) {
this.values += value + ' | ';
}
}
// #enddocregion key-up-component-2
//////////////////////////////////////////
// #docregion key-up-component-3
@Component({
selector: 'key-up3',
template: `
<input #box (keyup.enter)="values=box.value">
<p>{{values}}</p>
`
})
export class KeyUpComponent_v3 {
values='';
}
// #enddocregion key-up-component-3
//////////////////////////////////////////
// #docregion key-up-component-4
@Component({
selector: 'key-up4',
template: `
<input #box
(keyup.enter)="values=box.value"
(blur)="values=box.value">
<p>{{values}}</p>
`
})
export class KeyUpComponent_v4 {
values='';
}
// #enddocregion key-up-component-4

View File

@ -0,0 +1,25 @@
// #docregion
import {Component} from 'angular2/core';
// #docregion little-tour
@Component({
selector: 'little-tour',
template: `
<input #newHero
(keyup.enter)="addHero(newHero.value)"
(blur)="addHero(newHero.value); newHero.value='' ">
<button (click)=addHero(newHero.value)>Add</button>
<ul><li *ngFor="#hero of heroes">{{hero}}</li></ul>
`
})
export class LittleTourComponent {
heroes=['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
addHero(newHero:string) {
if (newHero) {
this.heroes.push(newHero);
}
}
}
// #enddocregion little-tour

View File

@ -0,0 +1,12 @@
// #docregion
import {Component} from 'angular2/core';
// #docregion loop-back-component
@Component({
selector: 'loop-back',
template:`
<input #box (keyup)="0">
<p>{{box.value}}</p>
`
})
export class LoopbackComponent { }
// #enddocregion loop-back-component

View File

@ -2,9 +2,9 @@
<html> <html>
<head> <head>
<title>User Input</title> <title>User Input</title>
<link rel="stylesheet" href="styles.css"> <link rel="stylesheet" href="styles.css">
<script src="../node_modules/systemjs/dist/system.src.js"></script> <script src="node_modules/systemjs/dist/system.src.js"></script>
<script src="../node_modules/angular2/bundles/angular2.dev.js"></script> <script src="node_modules/angular2/bundles/angular2.dev.js"></script>
<script> <script>
System.config({ System.config({
packages: {'app': {defaultExtension: 'js'}} packages: {'app': {defaultExtension: 'js'}}
@ -15,8 +15,6 @@
<body> <body>
<my-app>Loading...</my-app> <my-app>Loading...</my-app>
<hr>
<little-tour>Loading...</little-tour>
</body> </body>
</html> </html>

View File

@ -0,0 +1,8 @@
{
"description": "User Input",
"files": [
"!**/*.d.ts",
"!**/*.js"
],
"tags": ["input"]
}

View File

@ -1,14 +0,0 @@
<!-- #docregion click-me-button -->
<button (click)="onClickMe()">Click me!</button>
<!-- #enddocregion click-me-button -->
<h4>keyup loop-back component</h4>
<loop-back></loop-back>
<key-up></key-up>
<key-up2></key-up2>
<key-up3></key-up3>
<key-up4></key-up4>

View File

@ -1,147 +0,0 @@
// #docplaster
// imports formatted for dev guide only
// #docregion little-tour-of-heroes-app
import {bootstrap, Component, CORE_DIRECTIVES} from 'angular2/angular2';
// #enddocregion little-tour-of-heroes-app
// #docregion click-me-component
@Component({
selector: 'click-me',
template: '<button (click)="onClickMe()">Click me</button>'
})
class ClickMeComponent {
onClickMe(){
alert('You are my hero!')
}
}
// #enddocregion click-me-component
// #docregion loop-back-component
@Component({
selector: 'loop-back',
template: '<input #box (keyup)="0"> <p>{{box.value}}</p>'
})
class LoopbackComponent {
}
// #enddocregion loop-back-component
// #docregion key-up-component
@Component({
selector: 'key-up',
template: `
<h4>Give me some keys!</h4>
<div><input (keyup)="onKey($event)"></div>
<div>{{values}}</div>
`
})
class KeyUpComponent {
values='';
onKey(event) {
this.values += event.target.value + ' | ';
}
}
// #enddocregion key-up-component
// #docregion key-up2-component
@Component({
selector: 'key-up2',
template: `
<h4>Give me some more keys!</h4>
<div><input #box (keyup)="onKey(box.value)"></div>
<div>{{values}}</div>
`
})
class KeyUpComponentV2 {
values='';
onKey(value) {
this.values += value + ' | ';
}
}
// #enddocregion key-up2-component
// #docregion key-up3-component
@Component({
selector: 'key-up3',
template: `
<h4>Type away! Press [enter] when done.</h4>
<div><input #box (keyup.enter)="values=box.value"></div>
<div>{{values}}</div>
`
})
class KeyUpComponentV3 {
values='';
}
// #enddocregion key-up3-component
// #docregion key-up4-component
@Component({
selector: 'key-up4',
template: `
<h4>Type away! Press [enter] or mouse away when done.</h4>
<div>
<input #box
(keyup.enter)="values=box.value"
(blur)="values=box.value">
<div>
<div>{{values}}</div>
`
})
class KeyUpComponentV4 {
values='';
}
// #enddocregion key-up4-component
@Component({
selector: 'my-app',
templateUrl: 'app/app.html',
directives: [
CORE_DIRECTIVES,
ClickMeComponent,
KeyUpComponent, KeyUpComponentV2, KeyUpComponentV3, KeyUpComponentV4,
LoopbackComponent,
]
})
class AppComponent {
onClickMe(event){
let evtMsg = event ? ' Event target class is '+ event.target.className : '';
alert('Click me.'+evtMsg)
}
}
bootstrap(AppComponent);
///////////////////////////////////////////////////
// #docregion little-tour-of-heroes-app
@Component({
selector: 'little-tour',
template: `
<h4>Little Tour of Heroes</h4>
<input #new-hero
(keyup.enter)="addHero(newHero)"
(blur)="addHero(newHero)">
<button (click)=addHero(newHero)>Add</button>
<ul><li *ng-for="#hero of heroes">{{hero}}</li></ul>
`,
directives: [CORE_DIRECTIVES]
})
class LittleTour {
heroes=['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
addHero(newHero) {
if (newHero.value) {
this.heroes.push(newHero.value);
newHero.value = null; // clear the newHero textbox
}
}
}
bootstrap(LittleTour);
// #enddocregion little-tour-of-heroes-app

View File

@ -1,3 +0,0 @@
{
"files": ["!*.json"]
}

View File

@ -5,7 +5,7 @@ include ../../../../_includes/_util-fns
we want to know about it. These user actions all raise DOM events. we want to know about it. These user actions all raise DOM events.
In this chapter we learn to bind to those events using the Angular Event Binding syntax. In this chapter we learn to bind to those events using the Angular Event Binding syntax.
[Live Example](/resources/live-examples/user-input/ts/src/plnkr.html). [Live Example](/resources/live-examples/user-input/ts/plnkr.html).
:marked :marked
## Binding to User Input Events ## Binding to User Input Events
@ -15,7 +15,7 @@ include ../../../../_includes/_util-fns
The syntax is simple. We assign a template expression to the DOM event name, surrounded in parentheses. The syntax is simple. We assign a template expression to the DOM event name, surrounded in parentheses.
A click Event Binding makes for a quick illustration. A click Event Binding makes for a quick illustration.
+makeExample('user-input/ts/src/app/app.html', 'click-me-button')(format=".") +makeExample('user-input/ts/app/click-me.component.ts', 'click-me-button')(format=".", language="html")
<a id="click"></a> <a id="click"></a>
:marked :marked
@ -28,10 +28,8 @@ include ../../../../_includes/_util-fns
The identifers appearing within an expression belong to a specific context object. The identifers appearing within an expression belong to a specific context object.
That object is usually the Angular component that controls the template ... which it definitely is That object is usually the Angular component that controls the template ... which it definitely is
in this case because that snippet of HTML belongs to the following component: in this case because that snippet of HTML belongs to the following component:
<!--
These sample can be found in http://plnkr.co/edit/mr63T5 +makeExample('user-input/ts/app/click-me.component.ts', 'click-me-component', 'app/click-me.component.ts')
-->
+makeExample('user-input/ts/src/app/app.ts', 'click-me-component')
:marked :marked
The `onClickMe` in the template refers to the `onClickMe` method of the component. The `onClickMe` in the template refers to the `onClickMe` method of the component.
When the user clicks the button, Angular calls the component's `onClickMe` method. When the user clicks the button, Angular calls the component's `onClickMe` method.
@ -42,27 +40,43 @@ include ../../../../_includes/_util-fns
We can bind to all kinds of events. Let's bind to the "keyup" event of an input box and replay We can bind to all kinds of events. Let's bind to the "keyup" event of an input box and replay
what the user types back onto the screen. what the user types back onto the screen.
This time we'll both listen to an event and grab the user's input. This time we'll (1) listen to an event and (2) grab the user's input.
+makeExample('user-input/ts/src/app/app.ts', 'key-up-component') +makeExample('user-input/ts/app/keyup.components.ts', 'key-up-component-1-template', 'app/keyup.components.ts (template v.1)')
:marked
Angular makes an event object available in the **`$event`** variable
which we pass to the component's `onKey()` method.
The user data we want is in that variable somewhere.
+makeExample('user-input/ts/app/keyup.components.ts', 'key-up-component-1-class-no-type', 'app/keyup.components.ts (class v.1)')
:marked :marked
Angular makes an event object available in the **`$event`** variable. The user data we want is in that variable somewhere.
The shape of the `$event` object is determined by whatever raises the event. The shape of the `$event` object is determined by whatever raises the event.
The `keyup` event comes from the DOM so `$event` must be a [standard DOM event object](https://developer.mozilla.org/en-US/docs/Web/API/Event). The `keyup` event comes from the DOM so `$event` must be a [standard DOM event object](https://developer.mozilla.org/en-US/docs/Web/API/Event).
The `$event.target` gives us the The `$event.target` gives us the
[`HTMLInputElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement) which [`HTMLInputElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement) which
has a `value` property and that's where we find our user input data. has a `value` property and that's where we find our user input data.
With had this in mind when we passed `$event` to our `onKey()` component method. That method extracts the user's input and With had this in mind when we passed `$event` to our `onKey()` component method where we extract the user's input and
concatenates it to the previous user data that we're accumulating in the component's' `values` property. concatenate it to the previous user data that we're accumulating in the component's' `values` property.
We then use [interpolation](./template-syntax.html#interpolation) We then use [interpolation](./template-syntax.html#interpolation)
to display the `values` property back on screen. to display the accumulating `values` property back on screen.
Enter the letters "abc", backspace to remove them, and we should see: Enter the letters "abc", backspace to remove them, and we should see:
code-example(). code-example().
a | ab | abc | ab | a | | a | ab | abc | ab | a | |
figure.image-display figure.image-display
img(src='/resources/images/devguide/user-input/keyup1-anim.gif' alt="key up 1") img(src='/resources/images/devguide/user-input/keyup1-anim.gif' alt="key up 1")
<a id="keyup1"></a>
.l-sub-section
:marked
We cast the `$event` as an `any` type which means we've abandoned strong typing
to simplify our code. We generally prefer the strong typing that TypeScript affords.
We can rewrite the method, casting to HTML DOM objects like this.
+makeExample('user-input/ts/app/keyup.components.ts', 'key-up-component-1-class', 'app/keyup.components.ts (class v.1 - strongly typed )')
:marked
<br>Strong typing reveals a serious problem with passing a DOM event into the method:
too much awareness of template details, too little separation of concerns.
We'll address this problem in our next try at processing user keystrokes.
:marked :marked
.l-main-section .l-main-section
@ -74,28 +88,35 @@ figure.image-display
These variables grant us direct access to an element. These variables grant us direct access to an element.
We declare a local template variable by preceding an identifier with a hash/pound character (#). We declare a local template variable by preceding an identifier with a hash/pound character (#).
Let's demonstrate with a clever keystroke loopback in a single line of template HTML. Let's demonstrate with a clever keystroke loopback in an ultra-simple template.
We don't actually need a dedicated component to do this but we'll make one anyway. +makeExample('user-input/ts/app/loop-back.component.ts', 'loop-back-component', 'app/loop-back.component.ts')
+makeExample('user-input/ts/src/app/app.ts', 'loop-back-component')
:marked :marked
We've declared a template local variable named `box` on the `<input>` element. We've declared a template local variable named `box` on the `<input>` element.
The `box` variable is a reference to the `<input>` element itself which means we can The `box` variable is a reference to the `<input>` element itself which means we can
grab the input element's `value` and display it grab the input element's `value` and display it
with interpolation between `<p>` tags. The display updates as we type. *Voila!* with interpolation between `<p>` tags.
**This won't work at all unless we bind to an event**. Angular only updates the bindings The template is completely self contained. It doesn't bind to the component which does nothing.
(and therefore the screen)
if we do something in response to asynchronous events such as keystrokes. Type in the input box and watch the display update with each keystroke. *Voila!*
In this silly example we aren't really interested in the event at all. figure.image-display
But an Event Binding requires a template expression to evaluate when the event fires. img(src='/resources/images/devguide/user-input/keyup-loop-back-anim.gif' alt="loop back")
Many things qualify as expressions, none simpler than a one-character literal .l-sub-section
like the number zero. That's all it takes to keep Angular happy. We said it would be clever! :marked
**This won't work at all unless we bind to an event**.
Angular only updates the bindings (and therefore the screen)
if we do something in response to asynchronous events such as keystrokes.
That's why we bind the `keyup` event to an expression that does ... well, nothing.
We're binding to the number 0, the shortest expression we can think of.
That is all it takes to keep Angular happy. We said it would be clever!
:marked
That local template variable is intriguing. It's clearly easer to get to the textbox with that That local template variable is intriguing. It's clearly easer to get to the textbox with that
variable than to go through the `$event` object. Maybe we can re-write our previous variable than to go through the `$event` object. Maybe we can re-write our previous
"key-up" example using the variable to acquire the user's' input. Let's give it a try. "key-up" example using the variable to acquire the user's' input. Let's give it a try.
+makeExample('user-input/ts/src/app/app.ts', 'key-up2-component') +makeExample('user-input/ts/app/keyup.components.ts', 'key-up-component-2' ,'app/keyup.components.ts (v2)')
:marked :marked
That sure seems easier. That sure seems easier.
An especially nice aspect of this approach is that our component code gets clean data values from the view. An especially nice aspect of this approach is that our component code gets clean data values from the view.
@ -115,8 +136,10 @@ figure.image-display
Only then do we update the component's `values` property ... Only then do we update the component's `values` property ...
inside the event expression rather than in the component ... inside the event expression rather than in the component ...
because we can ... even if it is a dubious practice. because we *can* ... even if it is a dubious practice.
+makeExample('user-input/ts/src/app/app.ts', 'key-up3-component') +makeExample('user-input/ts/app/keyup.components.ts', 'key-up-component-3' ,'app/keyup.components.ts (v3)')
:marked
Here's how it works.
figure.image-display figure.image-display
img(src='/resources/images/devguide/user-input/keyup3-anim.gif' alt="key up 3") img(src='/resources/images/devguide/user-input/keyup3-anim.gif' alt="key up 3")
@ -130,7 +153,7 @@ figure.image-display
Let's fix that by listening to the input box's blur event as well. Let's fix that by listening to the input box's blur event as well.
+makeExample('user-input/ts/src/app/app.ts', 'key-up4-component') +makeExample('user-input/ts/app/keyup.components.ts', 'key-up-component-4' ,'app/keyup.components.ts (v4)')
.l-main-section .l-main-section
:marked :marked
@ -146,43 +169,62 @@ figure.image-display
figure.image-display figure.image-display
img(src='/resources/images/devguide/user-input/little-tour-anim.gif' alt="Little Tour of Heroes") img(src='/resources/images/devguide/user-input/little-tour-anim.gif' alt="Little Tour of Heroes")
:marked :marked
Below is the entire "Little Tour of Heroes" micro-app in a single component. Below is the "Little Tour of Heroes" component.
We'll call out the highlights after we bask briefly in its minimalist glory. We'll call out the highlights after we bask briefly in its minimalist glory.
<!--
This example in http://plnkr.co/edit/JWeIqq +makeExample('user-input/ts/app/little-tour.component.ts', 'little-tour', 'app/little-tour.component.ts')
-->
+makeExample('user-input/ts/src/app/app.ts', 'little-tour-of-heroes-app')
:marked :marked
We've seen almost everything here before. A few things are new or bear repeating. We've seen almost everything here before. A few things are new or bear repeating.
### **Beware of camelCase variable names** ### *newHero* template variable
We enter new hero names in the `<input>` element so we chose `newHero` to be the name of the local template variable.
The *newHero* template variable refers to the `<input>` element.
Unfortunately, we can't use that name when we declare the variable with (#).
The browser forces all attribute and element names to lowercase, turning what would be `#newHero` We can access `newHero` from any sibling or child of the `<input>` element.
into `#newhero` (all lowercase). We don't want a `newhero` variable name in our template expressions.
The Angular workaround is to spell the declaration in "kebab case". Angular translates "#new-hero"
to `newHero` for template expressions ... which is exactly what we want.
### **newHero refers to the `<input>` element**
We can access the `newHero` variable from any sibling or child of the `<input>` element.
When the user clicks the button, we don't need a fancy css selector to When the user clicks the button, we don't need a fancy css selector to
track down the textbox and extract its value. track down the textbox and extract its value.
The button simply passes the `newHero` textbox reference to its own click handling method.
That's a tremendous simplification, as anyone who's wrangled jQuery can confirm. ### Extract the input box *value*
We could have passed the `newHero` into the component's `addHero()` method.
Ready access to the `<input>` element also makes it easy for the `addHero` method
to clear the textbox after processing the new hero. But that would require `addHero` to pick its way through the `<input>` DOM element,
something we learned to dislike in our first try at a [*KeyupComponent*](#keyup1).
### **The *ng-for repeater**
The `ng-for` directive repeats the template as many times as there are heroes in the `heroes` list. Instead, we grab the input box *value* and pass *that* to `addHero()`.
We must remember to list `NgFor` among the directives used by the component's template The component knows nothing about HTML or DOM which is the way we like it.
by importing the `CORE_DIRECTIVES` constant and adding it to the
@Component decorator's `directives` array.
We learned about `NgFor` in the "[Displaying Data](displaying-data.html#ng-for)" chapter.
### Don't let template expressions be complex
We bound `(blur)` to *two* JavaScript statements.
We like the first one that calls `addHero`.
We do not like the second one that assigns an empty string to the input box value.
We did it for a good reason. We have to clear the input box after adding the new hero to the list.
The component has no way to do that itself &mdash; because it has no access to the
input box (our design choice).
Although it *works*, we are rightly wary of JavaScript in HTML.
Template expressions are powerful. We're supposed to use them responsibly.
Complex JavaScript in HTML is irresponsible.
Should we reconsider our reluctance to pass the input box into the component?
There should be a better third way. And there is as we'll see when we learn about `NgModel` in the [Forms](forms.html) chapter.
.l-main-section
:marked
## Source code
Here is all the code we talked about in this chapter.
+makeTabs(`
user-input/ts/app/click-me.component.ts,
user-input/ts/app/keyup.components.ts,
user-input/ts/app/loop-back.component.ts,
user-input/ts/app/little-tour.component.ts
`,'',
`click-me.component.ts,
keyup.components.ts,
loop-back.component.ts,
little-tour.component.ts`)
.l-main-section .l-main-section
:marked :marked

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB