2014-02-06 13:33:42 +00:00
|
|
|
@ngdoc tutorial
|
2016-03-27 21:32:36 +03:00
|
|
|
@name 12 - Event Handlers
|
2014-02-06 13:33:42 +00:00
|
|
|
@step 12
|
2013-10-08 10:43:01 -07:00
|
|
|
@description
|
|
|
|
|
|
|
|
|
|
<ul doc-tutorial-nav="12"></ul>
|
|
|
|
|
|
|
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
In this step, you will add a clickable phone image swapper to the phone details page.
|
2013-10-08 10:43:01 -07:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
* The phone details view displays one large image of the current phone and several smaller thumbnail
|
|
|
|
|
images. It would be great if we could replace the large image with any of the thumbnails just by
|
2017-01-24 17:23:54 +00:00
|
|
|
clicking on the desired thumbnail image. Let's have a look at how we can do this with AngularJS.
|
2013-10-08 10:43:01 -07:00
|
|
|
|
2014-02-06 14:02:18 +00:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
<div doc-tutorial-reset="12"></div>
|
2014-02-06 14:02:18 +00:00
|
|
|
|
2013-10-08 10:43:01 -07:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
## Component Controller
|
2013-10-08 10:43:01 -07:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
<br />
|
|
|
|
|
**`app/phone-detail/phone-detail.component.js`:**
|
2014-02-06 14:02:18 +00:00
|
|
|
|
|
|
|
|
```js
|
2016-03-27 21:32:36 +03:00
|
|
|
...
|
|
|
|
|
controller: ['$http', '$routeParams',
|
|
|
|
|
function PhoneDetailController($http, $routeParams) {
|
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
|
|
self.setImage = function setImage(imageUrl) {
|
|
|
|
|
self.mainImageUrl = imageUrl;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$http.get('phones/' + $routeParams.phoneId + '.json').then(function(response) {
|
|
|
|
|
self.phone = response.data;
|
|
|
|
|
self.setImage(self.phone.images[0]);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
...
|
2014-02-06 14:02:18 +00:00
|
|
|
```
|
2013-10-08 10:43:01 -07:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
In the `phoneDetail` component's controller, we created the `mainImageUrl` model property and set
|
|
|
|
|
its default value to the first phone image URL.
|
2013-10-08 10:43:01 -07:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
We also created a `setImage()` method (to be used as event handler), that will change the value of
|
|
|
|
|
`mainImageUrl`.
|
2013-10-08 10:43:01 -07:00
|
|
|
|
|
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
## Component Template
|
2013-10-08 10:43:01 -07:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
<br />
|
|
|
|
|
**`app/phone-detail/phone-detail.template.html`:**
|
2014-02-06 14:02:18 +00:00
|
|
|
|
|
|
|
|
```html
|
2016-03-27 21:32:36 +03:00
|
|
|
<img ng-src="{{$ctrl.mainImageUrl}}" class="phone" />
|
|
|
|
|
...
|
|
|
|
|
<ul class="phone-thumbs">
|
|
|
|
|
<li ng-repeat="img in $ctrl.phone.images">
|
|
|
|
|
<img ng-src="{{img}}" ng-click="$ctrl.setImage(img)" />
|
2013-10-08 10:43:01 -07:00
|
|
|
</li>
|
|
|
|
|
</ul>
|
2016-03-27 21:32:36 +03:00
|
|
|
...
|
2014-02-06 14:02:18 +00:00
|
|
|
```
|
2013-10-08 10:43:01 -07:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
We bound the `ngSrc` directive of the large image to the `$ctrl.mainImageUrl` property.
|
2013-10-08 10:43:01 -07:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
We also registered an {@link ng.directive:ngClick ngClick} handler with thumbnail images. When a
|
|
|
|
|
user clicks on one of the thumbnail images, the handler will use the `$ctrl.setImage()` method
|
|
|
|
|
callback to change the value of the `$ctrl.mainImageUrl` property to the URL of the clicked
|
|
|
|
|
thumbnail image.
|
2013-10-08 10:43:01 -07:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
<img class="diagram" src="img/tutorial/tutorial_12.png">
|
2013-10-08 10:43:01 -07:00
|
|
|
|
|
|
|
|
|
2018-02-27 17:33:42 +01:00
|
|
|
## Testing
|
2013-10-08 10:43:01 -07:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
To verify this new feature, we added two E2E tests. One verifies that `mainImageUrl` is set to the
|
|
|
|
|
first phone image URL by default. The second test clicks on several thumbnail images and verifies
|
|
|
|
|
that the main image URL changes accordingly.
|
2013-10-08 10:43:01 -07:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
<br />
|
|
|
|
|
**`e2e-tests/scenarios.js`:**
|
2013-10-08 10:43:01 -07:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
```js
|
|
|
|
|
...
|
2013-10-08 10:43:01 -07:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
describe('View: Phone detail', function() {
|
2013-10-08 10:43:01 -07:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
...
|
2013-10-08 10:43:01 -07:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
it('should display the first phone image as the main phone image', function() {
|
|
|
|
|
var mainImage = element(by.css('img.phone'));
|
2013-10-08 10:43:01 -07:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
expect(mainImage.getAttribute('src')).toMatch(/img\/phones\/nexus-s.0.jpg/);
|
|
|
|
|
});
|
2013-10-08 10:43:01 -07:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
it('should swap the main image when clicking on a thumbnail image', function() {
|
|
|
|
|
var mainImage = element(by.css('img.phone'));
|
|
|
|
|
var thumbnails = element.all(by.css('.phone-thumbs img'));
|
2013-10-08 10:43:01 -07:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
thumbnails.get(2).click();
|
|
|
|
|
expect(mainImage.getAttribute('src')).toMatch(/img\/phones\/nexus-s.2.jpg/);
|
2013-10-08 10:43:01 -07:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
thumbnails.get(0).click();
|
|
|
|
|
expect(mainImage.getAttribute('src')).toMatch(/img\/phones\/nexus-s.0.jpg/);
|
|
|
|
|
});
|
2014-02-06 14:02:18 +00:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
});
|
2013-10-08 10:43:01 -07:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
...
|
2014-02-06 14:02:18 +00:00
|
|
|
```
|
2013-10-08 10:43:01 -07:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
You can now rerun the tests with `npm run protractor`.
|
2014-04-27 09:09:59 +01:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
We also have to refactor one of our unit tests, because of the addition of the `mainImageUrl` model
|
|
|
|
|
property to the controller. As previously, we will use a mocked response.
|
2014-04-27 09:09:59 +01:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
<br />
|
|
|
|
|
**`app/phone-detail/phone-detail.component.spec.js`:**
|
2013-10-08 10:43:01 -07:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
```js
|
|
|
|
|
...
|
2013-10-08 10:43:01 -07:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
describe('controller', function() {
|
|
|
|
|
var $httpBackend, ctrl
|
|
|
|
|
var xyzPhoneData = {
|
|
|
|
|
name: 'phone xyz',
|
|
|
|
|
images: ['image/url1.png', 'image/url2.png']
|
|
|
|
|
};
|
2014-02-06 14:02:18 +00:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
beforeEach(inject(function($componentController, _$httpBackend_, _$routeParams_) {
|
|
|
|
|
$httpBackend = _$httpBackend_;
|
|
|
|
|
$httpBackend.expectGET('phones/xyz.json').respond(xyzPhoneData);
|
2013-10-08 10:43:01 -07:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
...
|
|
|
|
|
}));
|
2013-10-08 10:43:01 -07:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
it('should fetch phone details', function() {
|
|
|
|
|
expect(ctrl.phone).toBeUndefined();
|
2013-10-08 10:43:01 -07:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
$httpBackend.flush();
|
|
|
|
|
expect(ctrl.phone).toEqual(xyzPhoneData);
|
|
|
|
|
});
|
2014-04-27 09:09:59 +01:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
});
|
2014-04-27 09:09:59 +01:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
...
|
2014-04-27 09:09:59 +01:00
|
|
|
```
|
2013-10-08 10:43:01 -07:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
Our unit tests should now be passing again.
|
2013-10-08 10:43:01 -07:00
|
|
|
|
|
|
|
|
|
2018-02-27 17:33:42 +01:00
|
|
|
## Experiments
|
2014-02-06 14:02:18 +00:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
<div></div>
|
2014-01-22 14:19:00 +13:00
|
|
|
|
2017-01-24 17:23:54 +00:00
|
|
|
* Similar to the `ngClick` directive, which binds an AngularJS expression to the `click` event, there
|
2016-03-27 21:32:36 +03:00
|
|
|
are built-in directives for all native events, such as `dblclick`, `focus`/`blur`, mouse and key
|
|
|
|
|
events, etc.
|
2014-01-22 14:19:00 +13:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
Let's add a new controller method to the `phoneDetail` component's controller:
|
2014-01-22 14:19:00 +13:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
```js
|
|
|
|
|
self.onDblclick = function onDblclick(imageUrl) {
|
|
|
|
|
alert('You double-clicked image: ' + imageUrl);
|
2014-01-22 14:19:00 +13:00
|
|
|
};
|
2016-03-27 21:32:36 +03:00
|
|
|
```
|
2013-10-08 10:43:01 -07:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
and add the following to the `<img>` element in `phone-detail.template.html`:
|
2013-10-08 10:43:01 -07:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
```html
|
|
|
|
|
<img ... ng-dblclick="$ctrl.onDblclick(img)" />
|
|
|
|
|
```
|
2013-10-08 10:43:01 -07:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
Now, whenever you double-click on a thumbnail, an alert pops-up. Pretty annoying!
|
2013-10-08 10:43:01 -07:00
|
|
|
|
|
|
|
|
|
2018-02-27 17:33:42 +01:00
|
|
|
## Summary
|
2013-10-08 10:43:01 -07:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
With the phone image swapper in place, we are ready for {@link step_13 step 13} to learn an even
|
|
|
|
|
better way to fetch data.
|
2013-10-08 10:43:01 -07:00
|
|
|
|
2014-05-19 02:42:30 +02:00
|
|
|
|
2016-03-27 21:32:36 +03:00
|
|
|
<ul doc-tutorial-nav="12"></ul>
|