angular-cn/public/docs/ts/latest/guide/user-input.jade

394 lines
21 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

include ../_util-fns
:marked
When the user clicks a link, pushes a button, or enters text
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.
当用户点击链接、按下按钮或者输入文字时我们得知道发生了什么。这些用户动作都会产生DOM事件。
本章中我们将学习如何使用Angular事件绑定语法来绑定这些事件。
[Run the live example](/resources/live-examples/user-input/ts/plnkr.html)
[运行在线例子](/resources/live-examples/user-input/ts/plnkr.html)
:marked
## Binding to user input events
## 绑定到用户输入事件
We can use [Angular event bindings](./template-syntax.html#event-binding)
to respond to [any DOM event](https://developer.mozilla.org/en-US/docs/Web/Events).
我们可以使用[Angular事件绑定](./template-syntax.html#event-binding)机制来响应[任何DOM事件](https://developer.mozilla.org/en-US/docs/Web/Events)。
The syntax is simple. We surround the DOM event name in parentheses and assign a quoted template statement to it.
As an example, here's an event binding that implements a click handler:
语法非常简单。我们只要把DOM事件的名字包裹在圆括号中然后用一个放在引号中的“模板语句”对它赋值就可以了。
下面的例子中,点击事件被绑定到了一个事件处理方法上:
+makeExample('user-input/ts/app/click-me.component.ts', 'click-me-button')(format=".", language="html")
<a id="click"></a>
:marked
The `(click)` to the left of the equal sign identifies the button's click event as the **target of the binding**.
The text within quotes on the right is the **template statement** in which we
respond to the click event by calling the component's `onClickMe` method. A [template statement](./template-syntax.html#template-statements) is a subset
of JavaScript with restrictions and a few added tricks.
等号左边的`(click)`表示把该按钮的点击事件作为**绑定目标**。
等号右边,引号中的文本是一个**模板语句**,在这里我们通过调用组件的`onClickMe`方法来响应这个点击事件。
[模板语句](./template-syntax.html#template-statements)的语法是JavaScript语法的一个受限子集但它也添加了少量“小花招”。
When writing a binding we must be aware of a template statement's **execution context**.
The identifiers appearing within a statement belong to a specific context object.
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:
写绑定时,我们必须知道模板语句的**执行上下文**。
出现在模板语句中的各个标识符,全都属于一个特定的上下文对象。
这个对象通常都是控制此模板的Angular组件…… 在本例中它很明确因为这段HTML片段属于下面这个组件
+makeExample('user-input/ts/app/click-me.component.ts', 'click-me-component', 'app/click-me.component.ts')(format=".")
:marked
When the user clicks the button, Angular calls the component's `onClickMe` method.
当用户点击此按钮时Angular调用组件的`onClickMe`方法。
.l-main-section
:marked
## Get user input from the $event object
## 通过$event对象取得用户输入
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.
我们可以绑定到所有类型的事件。让我们试试绑定到一个输入框的keyup事件并且把用户输入的东西回显到屏幕上。
This time we'll (1) listen to an event and (2) grab the user's input.
这次,我们将(1)监听一个事件(2)捕获用户输入。
+makeExample('user-input/ts/app/keyup.components.ts', 'key-up-component-1-template', 'app/keyup.components.ts (模板 v.1)')(format=".")
: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.
Angular把事件对象存入**`$event`**变量中,也就是我们传到组件的`onKey()`方法中的那个。
我们希望取得的用户数据就在该变量中的某个地方。
+makeExample('user-input/ts/app/keyup.components.ts', 'key-up-component-1-class-no-type', 'app/keyup.components.ts (类 v.1)')(format=".")
:marked
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 `$event.target` gives us an
[`HTMLInputElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement), which
has a `value` property that contains our user input data.
`$event`对象的形态取决于所触发的事件。
这个`keyup`事件来自DOM所以`$event`必须是一个[标准的DOM事件对象](https://developer.mozilla.org/en-US/docs/Web/API/Event)。
`$event.target`给了我们一个[`HTMLInputElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement)对象,它有一个`value`属性,是用户所输入的数据。
The `onKey()` component method is where we extract the user's input
from the event object, adding that input to the list of user data that we're accumulating in the component's `values` property.
We then use [interpolation](./template-syntax.html#interpolation)
to display the accumulating `values` property back on screen.
组件的`onKey()`方法是用来从事件对象中提取出用户输入的,它把输入的值累加组件的`values`属性里,我们利用这个属性来累计用户数据。
然后我们使用[插值表达式](./template-syntax.html#interpolation)来把存放累加结果的`values`属性回显到屏幕上。
Enter the letters "abc", and then backspace to remove them.
Here's what the UI displays:
输入字母"abc",然后用退格键删除它们。
UI上的显示如下
code-example().
a | ab | abc | ab | a | |
figure.image-display
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.
我们把`$event`变量声明成了`any`类型这意味着我们放弃了强类型以简化代码。我们更建议您好好使用TypeScript提供的强类型机制。
我们可以重写此方法把它声明为HTML DOM对象就像这样
+makeExample('user-input/ts/app/keyup.components.ts', 'key-up-component-1-class', 'app/keyup.components.ts (类 v.1 - 强类型版本)')(format=".")
: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.
<br>使用强类型后我们就能看出直接把DOM事件对象传到方法里的做法有一个严重的问题过多模板细节太少关注点分离。(译注onKey不应该理会模板的实现细节只接收传入字符串。需要强制转换类型是代码的坏味道。)
We'll address this problem in our next try at processing user keystrokes.
我们将在下次再处理用户按键时处理这个问题。
:marked
.l-main-section
:marked
## Get user input from a template reference variable
## 从一个模板引用变量中获得用户输入
There's another way to get the user data without the `$event` variable.
还有另一种方式,不用通过`$event`变量来获得用户数据。
Angular has a syntax feature called [**template reference variables**](./template-syntax.html#ref-vars).
These variables grant us direct access to an element.
We declare a template reference variable by preceding an identifier with a hash/pound character (#).
Angular有一个叫做[**模板引用变量**](./template-syntax.html#ref-vars)的语法特性。
这些变量给了我们直接访问元素的能力。
通过在标识符前加上井号(#),我们就能定义一个模板引用变量。
Here's an example of using a template reference variable
to implement a clever keystroke loopback in an ultra-simple template.
下面的例子就通过使用局部模板变量,在一个超简单的模板中实现了一个聪明的按键反馈循环。
+makeExample('user-input/ts/app/loop-back.component.ts', 'loop-back-component', 'app/loop-back.component.ts')(format=".")
:marked
We've declared a template reference variable named `box` on the `<input>` element.
The `box` variable is a reference to the `<input>` element itself, which means we can
grab the input element's `value` and display it
with interpolation between `<p>` tags.
我们在`<input>`元素上定义了一个名叫`box`的模板引用变量。
`box`变量引用的就是`<input>`元素本身这意味着我们可以获得input元素的`value`值,并通过插值表达式把它显示在`<p>`标签中。
The template is completely self contained. It doesn't bind to the component,
and the component does nothing.
本模板完全是自包含的。它不需要绑定到组件,即使绑定了,组件也不需要额外做什么。
Type in the input box, and watch the display update with each keystroke. *Voila!*
在输入框中输入,就会看到每次按键时,显示也随之更新了。*完工!*
figure.image-display
img(src='/resources/images/devguide/user-input/keyup-loop-back-anim.gif' alt="反馈")
.l-sub-section
: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.
如果我们在异步事件(如击键)的响应中做点什么Angular只会更新绑定(并最终影响到屏幕)。
That's why we bind the `keyup` event to a statement that does ... well, nothing.
We're binding to the number 0, the shortest statement we can think of.
That is all it takes to keep Angular happy. We said it would be clever!
这就是我们为什么需要把`keyup`事件绑定到一个语句,它做了……好吧,它啥也没做。
它被绑定到了数字0因为这是我们所能想到的最短语句。
这么做完全是为了讨好Angular。我们说过会很聪明
:marked
That template reference variable is intriguing. It's clearly easier to get to the textbox with that
variable than to go through the `$event` object. Maybe we can rewrite our previous
keyup example so that it uses the variable to get the user's input. Let's give it a try.
模板引用变量很有意思。它用一个变量就简洁明了的获得了文本框,而不再需要通过`$event`对象。
也许我们可以重写前面的keyup范例以便它能用这个变量来获得用户输入。我们这就试试看。
+makeExample('user-input/ts/app/keyup.components.ts', 'key-up-component-2' ,'app/keyup.components.ts (v2)')(format=".")
:marked
That sure seems easier.
An especially nice aspect of this approach is that our component code gets clean data values from the view.
It no longer requires knowledge of the `$event` and its structure.
看起来真是简单多了。
该方案最漂亮的一点是:我们的组件代码从视图中获得了干干净净的数据值。再也不用了解`$event`变量及其结构了。
<a id="key-event"></a>
.l-main-section
:marked
## Key event filtering (with `key.enter`)
## 按键事件过滤(通过`key.enter`)
Perhaps we don't care about every keystroke.
Maybe we're only interested in the input box value when the user presses Enter, and we'd like to ignore all other keys.
When we bind to the `(keyup)` event, our event handling statement hears *every keystroke*.
We could filter the keys first, examining every `$event.keyCode`, and update the `values` property only if the key is Enter.
或许我们并不关心每一次按键,只在用户按下回车(enter)键的时候,我们才会关心输入框的值,所有其它按键都可以忽略。
当绑定到`(keyup)`事件的时候,我们的事件处理语句会听到 *每一次按键* 。
我们应该先过滤一下按键,比如每一个`$event.keyCode`,并且只有当这个按键是回车键的时候才更新`values`属性。
Angular can filter the key events for us. Angular has a special syntax for keyboard events.
We can listen for just the Enter key by binding to Angular's `keyup.enter` pseudo-event.
Angular可以为我们过滤键盘事件。Angular有一个关于键盘事件的特殊语法。
通过绑定到Angular的`keyup.enter`伪事件,我们可以只监听回车键的事件。
Only then do we update the component's `values` property. (In this example,
the update happens inside the event binding statement. A better practice
would be to put the update code in the component.)
只有在这种情况下,我们才更新组件的`values`属性。
(在这个例子中,更新代码是写在事件绑定语句中的。但在实践中更好的方式是把更新代码放到组件中。)
+makeExample('user-input/ts/app/keyup.components.ts', 'key-up-component-3' ,'app/keyup.components.ts (v3)')(format=".")
:marked
Here's how it works.
下面展示了它是如何工作的。
figure.image-display
img(src='/resources/images/devguide/user-input/keyup3-anim.gif' alt="key up 3")
.l-main-section
:marked
## On blur
## blur(失去焦点)事件
Our previous example won't transfer the current state of the input box if the user mouses away and clicks
elsewhere on the page. We update the component's `values` property only when the user presses Enter
while the focus is inside the input box.
前一个例子中,如果用户移开了鼠标,并且点击了页面中别的地方,它不会传出输入框的值。而我们希望它在失去焦点时的行为也等同于按下回车键。
只有在输入框得到焦点,并且用户按下了回车键的时候,我们才能更新组件的`values`属性。
Let's fix that by listening to the input box's blur event as well.
我们来修正这个问题 —— 通过同时监听输入框失去焦点的事件。
+makeExample('user-input/ts/app/keyup.components.ts', 'key-up-component-4' ,'app/keyup.components.ts (v4)')(format=".")
.l-main-section
:marked
## Put it all together
## 把它们放在一起
We learned how to [display data](./displaying-data.html) in the previous chapter.
We've acquired a small arsenal of event binding techniques in this chapter.
在前一章中,我们学到了如何[显示数据](./displaying-data.html)。
在本章中,我们得到了一个关于事件绑定技术的小型武器库。
Let's put it all together in a micro-app
that can display a list of heroes and add new heroes to that list.
The user can add a hero by first typing in the input box and then
pressing Enter, clicking the Add button, or clicking elsewhere on the page.
让我们在一个微型应用中把它们放在一起,它能显示一个英雄列表,并且把新的英雄加到列表中。
用户可以通过下列步骤添加英雄:先在输入框中输入,然后按下回车键、按下“添加”按钮或点击页面中的其它地方。
figure.image-display
img(src='/resources/images/devguide/user-input/little-tour-anim.gif' alt="简版英雄指南")
:marked
Below is the "Little Tour of Heroes" component.
We'll call out the highlights after we bask briefly in its minimalist glory.
下面就是“简版英雄指南”组件。
短暂看一下即可,我们接下来将对它们分别讲解。
+makeExample('user-input/ts/app/little-tour.component.ts', 'little-tour', 'app/little-tour.component.ts')(format=".")
:marked
We've seen almost everything here before. A few things are new or bear repeating.
我们在这里几乎看到了以前接触过的每一个概念。有少量新东西,其它是复习。
### Use template variables to refer to elements
### 使用模板变量引用元素
The `newHero` template variable refers to the `<input>` element.
We can use `newHero` from any sibling or child of the `<input>` element.
`newHero`模板变量引用了`<input>`元素。
我们可以在`<input>`元素的任何兄弟节点或子节点中使用`newHero`。
Getting the element from a template variable makes the button click handler
simpler. Without the variable, we'd have to use a fancy CSS selector
to find the input element.
从模板变量中获得元素可以让按钮的点击click事件处理变得更简单。
如果没有变量我们就不得不使用“奇怪的”CSS选择器来查找这个input元素。
### Pass values, not elements
### 传入值,不要传元素
We could have passed the `newHero` into the component's `addHero` method.
我们可以把`newHero`传入组件的`addHero`方法。
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 [keyup component](#keyup1).
但那需要`addHero`通过访问`<input>`DOM元素的方式先取得它 —— 也就是我们以前在[keyup组件](#keyup1)中学过的那种讨厌的方式。
Instead, we grab the input box *value* and pass *that* to `addHero`.
The component knows nothing about HTML or the DOM, which is the way we like it.
该怎么做呢?我们该取得输入框的*值value*,并把它传给`addHero`。
该组件不需要知道关于HTML或DOM的任何知识我们更喜欢这种方式。
### Keep template statements simple
### 保持模板中的语句简洁
We bound `(blur)` to *two* JavaScript statements.
我们把`(blur)`事件绑定到了*两条*JavaScript语句。
We like the first one, which calls `addHero`.
We do not like the second one, which assigns an empty string to the input box value.
我们喜欢前一条,它调用了`addHero`。
我们不喜欢第二条,它把一个空值赋值给了输入框的`value`。
The second statement exists 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 because it has no access to the
input box (our design choice).
第二条语句的存在理由很充分:在把新的英雄加入列表中之后,我们得清除输入框的值。
组件自己做不到这一点,它不能访问输入框(我们的设计选择)。
Although the example *works*, we are rightly wary of JavaScript in HTML.
Template statements are powerful. We're supposed to use them responsibly.
Complex JavaScript in HTML is irresponsible.
虽然范例*能工作*但我们得对HTML中的JavaScript保持警惕。
模板语句很强大,所以我们更得认真负责的使用它们。
显然在HTML中使用复杂的JavaScript是不负责任的表现。
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.
应该有一种更好的第三条路。恩,确实有!当我们在[表单](forms.html)一章学到`ngModel`时就明白了。
.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
:marked
## Summary
## 小结
We've mastered the basic primitives for responding to user input and gestures.
As powerful as these primitives are, they are a bit clumsy for handling
large amounts of user input. We're operating down at the low level of events when
we should be writing two-way bindings between data entry fields and model properties.
我们已经掌握了响应用户输入和操作的基础技术。
虽然这些基础技术确实强大,但在处理大量用户输入时难免显得笨拙。
我们在事件底层操作,但是真正应该做的是:在数据输入字段和模型属性之间建立双向数据绑定。
Angular has a two-way binding called `NgModel`, which we'll learn about
in the `Forms` chapter.
Angular有一种叫做`NgModel`的双向数据绑定机制,我们将在`表单`一章中学到它。