From 840439770be81a5a51812f5c72d5a57b854e6a95 Mon Sep 17 00:00:00 2001 From: Ward Bell Date: Mon, 19 Oct 2015 20:14:51 -0700 Subject: [PATCH] (docs) overhaul user-input --- public/docs/ts/latest/guide/_data.json | 2 +- .../docs/ts/latest/guide/template-syntax.jade | 4 +- public/docs/ts/latest/guide/user-input.jade | 401 ++++++++++-------- .../devguide/user-input/little-tour.png | Bin 0 -> 20354 bytes 4 files changed, 234 insertions(+), 173 deletions(-) create mode 100644 public/resources/images/devguide/user-input/little-tour.png diff --git a/public/docs/ts/latest/guide/_data.json b/public/docs/ts/latest/guide/_data.json index b8684eec1d..ed05038e70 100644 --- a/public/docs/ts/latest/guide/_data.json +++ b/public/docs/ts/latest/guide/_data.json @@ -16,7 +16,7 @@ "user-input": { "title": "User Input", - "intro": "DOM events drive user input in Angular. You can use the native events like click, mouseover, and keyup. Angular uses a special syntax to register events to DOM elements. This section covers all the ins and outs of using the event syntax." + "intro": "User input triggers DOM events. We listen to those events with EventBindings that funnel updated values back into our components and models." }, "pipes": { diff --git a/public/docs/ts/latest/guide/template-syntax.jade b/public/docs/ts/latest/guide/template-syntax.jade index dcee33f513..076ab63872 100644 --- a/public/docs/ts/latest/guide/template-syntax.jade +++ b/public/docs/ts/latest/guide/template-syntax.jade @@ -30,7 +30,7 @@ include ../../../../_includes/_util-fns >[* and <template>](#star-template) - >[Local variables](#local-vars) + >[Local template variables](#local-vars) >[Input and Output Properties](#inputs-outputs) @@ -124,7 +124,7 @@ code-example(format="" language="html" escape="html"). The component itself is usually the expression *context* in which case the template expression usually references that component. The expression context may include an object other than the component. - A [template local variable](#local-vars) is one such supplemental context object; + A [local template variable](#local-vars) is one such supplemental context object; we’ll discuss that option below. Another is the **`$event`** variable that contains information about an event raised on an element; diff --git a/public/docs/ts/latest/guide/user-input.jade b/public/docs/ts/latest/guide/user-input.jade index f6280732dd..c8e5cfb7c7 100644 --- a/public/docs/ts/latest/guide/user-input.jade +++ b/public/docs/ts/latest/guide/user-input.jade @@ -1,183 +1,244 @@ -.l-main-section - h2#section-responding-to-user-input Responding to user input with event syntax +:markdown + When the user clicks a link, pushes a button, or types on the keyboard + 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. - p. - You can make your application respond to user input by using the event syntax. The event syntax starts with an event name surrounded by parenthesis: (event). A controller function is then assigned to the event name: (event)="controllerFn()". - p. - For a particular control like an input you can have it call methods on your controller on keyup event like so: +:markdown + ## Binding to User Input Events - code-example(language="html"). - <input (keyup)="myControllerMethod()"> - - h3#local-variables Local variables - p. - As in previous examples, you can make element references available to other parts of the template as a local - variable using the #var syntax. With this and events, we can do the old "update text as you type" example: - - code-example(language="html"). - <input #myname (keyup)> - <p>{{myname.value}}</p> - - p.text-body(ng-non-bindable). - The #myname creates a local variable in the template that we'll refer to below in the - <p> element. The (keyup) tells Angular to trigger updates when it gets a keyup - event. And the {{myname.value}} binds the text node of the <p> element to the - input's value property. - p Let's do something a little more complex where users enter items and add them to a list like this: - - figure.image-display - img(src='/resources/images/examples/user-input-example1.png' alt="Example of Todo App") - - -.l-ain-section - h2#section-create-an-array-property Create an array property - p. - With the default bootstrapping in place, create a controller class that will manage interactions with the - list. Inside the controller, add an array with an initial list of items. Then add a method that pushes new items - on the array when called. - - code-tabs - code-pane(language="javascript" name="TypeScript" format="linenums"). - //TypeScript - class TodoList { - todos: Array<string>; - constructor() { - this.todos = ["Eat Breakfast", "Walk Dog", "Breathe"]; - } - addTodo(todo: string) { - this.todos.push(todo); - } - } - code-pane(language="javascript" name="ES5" format="linenums"). - //ES5 - function TodoList() { - this.todos = ["Eat Breakfast", "Walk Dog", "Breathe"]; - this.addTodo = function(todo) { - this.todos.push(todo); - }; - } - -.callout.is-helpful - header Production Best Practice - p. - As with the previous example, in a production application you will separate your model out into another class - and inject it into TodoList. We've omitted it here for brevity. + We can listen to [any DOM event](https://developer.mozilla.org/en-US/docs/Web/Events) + with an [Angular Event Binding](./template-syntax.html#event-binding). + + 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. + +code-example(language="html" ). + <button (click)="onClickMe()">Click me</button> +:markdown + 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 expression**" in which we + respond to the click event by calling the component's `onClickMe` method. A [template expression](./template-syntax.html#template-expressions) is a subset + of JavaScript with a few added tricks. + + When writing a binding we must be aware of a template expression's **execution context**. + 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 + in this case because that snippet of HTML belongs to the following component: + + ``` + @Component({ + selector: 'click-me', + template: '' + }) + class ClickMeComponent { + onClickMe(){ + alert('You are my hero!') + } + } + ``` + 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. .l-main-section - h2#section-display-the-list-of-todos Display the list of todos - p. - Using the *ng-for iterator, create an <li> for each item in the todos array and set - its text to the value. +:markdown + ## Get user input from the $event object + 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. + + This time we'll both listen to an event and grab the user's input. +code-example(format="linenums" language="html" ). + @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 + ' | '; + } + } +:markdown + 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 `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 + [`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. + + With had this in mind when we passed `$event` to our `onKey()` component method. That method extracts the user's input and + concatenates it to the previous user data that we're accumulating in the component's' `values` property. + We then use [interpolation](./template-syntax.html#interpolation) + to display the `values` property back on screen. + + Enter the letters "abcd", backspace to remove them, and we should see: +code-example(). + a | ab | abc | abcd | abc | ab | a | | - code-example(language="html" format="linenums"). - <ul> - <li *ng-for="#todo of todos"> - {{ todo }} - </li> - </ul> +:markdown .l-main-section - h2#section-add-todos-to-the-list Add todos to the list via button click - p. - Now, add a text input and a button for users to add items to the list. As you saw above, you can create a local - variable reference in your template with #varname. Call it #todotext here. +:markdown + ## Get user input from a local template variable + There's another way to get the user data without the `$event` variable. + + Angular has syntax feature called [**local template variables**](./template-syntax.html#local-vars). + These variables grant us direct access to an element. + 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. + We don't actually need a dedicated component to do this but we'll make one anyway. + +code-example(format="linenums" language="html" ). + @Component({ + selector: 'loop-back', + template: `<input #box (keyup)="0"> <p>{{box.value}}</p>` + }) + class LoopbackComponent { + } - code-example(language="html" format="linenums"). - <input #todotext> - p. - Lastly, specify the target of the click event binding as your controller's addTodo() method and pass - it the value. Since you created a reference called todotext, you can get the value with - todotext.value. +:markdown + We've declared a template local variable named `box` on the `` element. + The `box` variable is a reference to the `` element itself which means we can + grab the input element's `value` and display it + with interpolation between `

` tags. The display updates as we type. *Voila!* + + **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. + + In this silly example we aren't really interested in the event at all. + But an Event Binding requires a template expression to evaluate when the event fires. + Many things qualify as expressions, none simpler than a one-character literal + like the number zero. That's all it takes to keep Angular happy. We said it would be clever! + + 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 + example using the variable to acquire the user's' input. Let's give it a try. +code-example(format="linenums" language="html" ). + @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 + ' | '; + } + } +:markdown + 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. + - code-example(language="html" format="linenums"). - <button (click)="addTodo(todotext.value)">Add Todo</button> + +.l-main-section +:markdown + ## 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. + + Let's put it all together in a micro-app + that can display a list of heroes and add new heroes to that list. - p And then create the doneTyping() method on TodoList and handle adding the todo text. - - code-example(language="javascript" format="linenums"). - doneTyping($event) { - if($event.which === 13) { - this.addTodo($event.target.value); - $event.target.value = null; +figure.image-display + img(src='/resources/images/devguide/user-input/little-tour.png' alt="Little Tour of Heroes") +:markdown + Below is the entire "Little Tour of Heroes" micro-app in a single component. + We'll call out the highlights after we bask briefly in its minimalist glory. + +code-example(format="linenums" language="html" ). + import {bootstrap, Component CORE_DIRECTIVES} from 'angular2/core' + + @Component({ + selector: 'little-tour', + template: ` + <h4>Little Tour of Heroes</h4> + <input #new-hero (keyup.enter)="addHero(newHero)"> + <button (click)=addHero(newHero)>Add</button> + <ul><li *ng-for="#hero of heroes">{{hero}}</li></ul> + <div>There are so many heroes!</div> + `, + 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); + +:markdown + We've seen almost everything here before. A few things are new or bear repeating. + + ### **Beware of camelCase variable names** + We enter new hero names in the `` element so we chose `newHero` to be the name of the local template variable. + + 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` + into `#newhero`. We don't want a `newhero` variable name in our template expressions. + The Angular workaround is to spell the declaration in "snake case". When Angular encounters "#new-hero",it translates + that to `newHero` for template expressions ... which is exactly what we want. + + ### **keyup.enter - a KeyEvent filter** + We'll add a hero name when the user clicks the "Add" button or hits the enter key. We ignore all other keys. + If we bind to `(keyup)` our event handling expression hears every key event. We'd have to + examine every `$event.keyCode` and respond only if the value is "Enter". + + 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. + Then either the `keyup.enter` or the button click event + can invoke the component's `addHero` method. + + ### **newHero refers to the `` element** + We can access the `newHero` variable from any sibling or child of the `` element. + When the user clicks the button, we don't need a fancy css selector to + 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. + + Ready access to the `` element also makes it easy for the `addHero` method + to clear the textbox after processing the new hero. + + ### **The *ng-for repeater** + The `ng-for` directive repeats the template as many times as there are heroes in the `heroes` list. + We must remember to list `NgFor` among the directives used by the component's template + 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)" chapter. + + .l-main-section - h2#section-final-code Final Code - - code-tabs - code-pane(language="javascript" name="TypeScript" format="linenums"). - //TypeScript - import {Component, View, bootstrap, NgFor, NgIf} from 'angular2/angular2'; - - @Component({ - selector: 'todo-list' - template: ` - <ul> - <li *ngfor="#todo of todos"> - {{ todo }} - </li> - </ul> - - <input #todotext (keyup)="doneTyping($event)"> - <button (click)="addTodo(todotext.value)">Add Todo</button> - `, - directives: [NgFor, NIf] - }) - class TodoList { - todos: Array<string>; - - constructor() { - this.todos = ["Eat Breakfast", "Walk Dog", "Breathe"]; - } - - addTodo(todo: string) { - this.todos.push(todo); - } - - doneTyping($event) { - if($event.which === 13) { - this.addTodo($event.target.value); - $event.target.value = null; - } - } - } - - bootstrap(TodoList); - code-pane(language="javascript" name="ES5" format="linenums"). - //ES5 - function TodoList() { - this.todos = ["Eat Breakfast", "Walk Dog", "Breathe"]; - - this.addTodo = function(todo) { - this.todos.push(todo); - }; - - this.doneTyping = function($event) { - if($event.which === 13) { - this.addTodo($event.target.value); - $event.target.value = null; - } - } - } - - TodoList.annotations = [ - new angular.ComponentAnnotation({ - selector: "todo-list" - }), - new angular.ViewAnnotation({ - template: - '<ul>' + - '<li *ng-for="#todo of todos">' + - '{{ todo }}' + - '</li>' + - '</ul>' + - '<input #textbox (keyup)="doneTyping($event)">' + - '<button (click)="addTodo(textbox.value)">Add Todo</button>', - directives: [angular.NgFor, angular.NgIf] - }) - ]; - - document.addEventListener("DOMContentLoaded", function() { - angular.bootstrap(TodoList); - }); - +:markdown + ## Next Steps + + 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` and we learn about it + in the `Forms` chapter. + + + \ No newline at end of file diff --git a/public/resources/images/devguide/user-input/little-tour.png b/public/resources/images/devguide/user-input/little-tour.png new file mode 100644 index 0000000000000000000000000000000000000000..57585058432d9f8f0aede081626f8ff96b3c687a GIT binary patch literal 20354 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 literal 0 HcmV?d00001