Skip to content
This repository was archived by the owner on Feb 26, 2024. It is now read-only.

step-15 Accessibility #341

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,18 @@ git diff step-?..step-?
- CSS transition animations.
- CSS keyframe animations.
- JavaScript-based animations.

### step-15 _Accessibility (a11y)_

- Add labels to the search and order fields.
- Add accessibility plugin for Protractor.
- Add missing alt attributes in the phone detail.
- Add aria live regions to inform the user about results after searching and filtering elements.
- Improve access via keyboard:
- Navigate between the images in a phone detail.
- Add headings elements.
- Add information about checkmarks in the phone detail specs.



## Development with `angular-phonecat`
Expand Down Expand Up @@ -276,4 +288,4 @@ For more information on AngularJS, please check out https://angularjs.org/.
[karma]: https://karma-runner.github.io/
[node]: https://nodejs.org/
[protractor]: http://www.protractortest.org/
[selenium]: http://docs.seleniumhq.org/
[protractor-accessibility-plugin]: https://github.com/angular/protractor-accessibility-plugin
67 changes: 67 additions & 0 deletions app/app.animations.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/* Animate `ngRepeat` in `phoneList` component */
.phone-list-item.ng-enter,
.phone-list-item.ng-leave,
.phone-list-item.ng-move {
overflow: hidden;
transition: 0.5s linear all;
}

.phone-list-item.ng-enter,
.phone-list-item.ng-leave.ng-leave-active,
.phone-list-item.ng-move {
height: 0;
margin-bottom: 0;
opacity: 0;
padding-bottom: 0;
padding-top: 0;
}

.phone-list-item.ng-enter.ng-enter-active,
.phone-list-item.ng-leave,
.phone-list-item.ng-move.ng-move-active {
height: 120px;
margin-bottom: 20px;
opacity: 1;
padding-bottom: 4px;
padding-top: 15px;
}

/* Animate view transitions with `ngView` */
.view-container {
position: relative;
}

.view-frame {
margin-top: 20px;
}

.view-frame.ng-enter,
.view-frame.ng-leave {
background: white;
left: 0;
position: absolute;
right: 0;
top: 0;
}

.view-frame.ng-enter {
animation: 1s fade-in;
z-index: 100;
}

.view-frame.ng-leave {
animation: 1s fade-out;
z-index: 99;
}

@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}

@keyframes fade-out {
from { opacity: 1; }
to { opacity: 0; }
}

/* Older browsers might need vendor-prefixes for keyframes and animation! */
43 changes: 43 additions & 0 deletions app/app.animations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
'use strict';

angular.
module('phonecatApp').
animation('.phone', function phoneAnimationFactory() {
return {
addClass: animateIn,
removeClass: animateOut
};

function animateIn(element, className, done) {
if (className !== 'selected') return;

element.css({
display: 'block',
position: 'absolute',
top: 500,
left: 0
}).animate({
top: 0
}, done);

return function animateInEnd(wasCanceled) {
if (wasCanceled) element.stop();
};
}

function animateOut(element, className, done) {
if (className !== 'selected') return;

element.css({
position: 'absolute',
top: 0,
left: 0
}).animate({
top: -500
}, done);

return function animateOutEnd(wasCanceled) {
if (wasCanceled) element.stop();
};
}
});
18 changes: 18 additions & 0 deletions app/app.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
'use strict';

angular.
module('phonecatApp').
config(['$locationProvider' ,'$routeProvider',
function config($locationProvider, $routeProvider) {
$locationProvider.hashPrefix('!');

$routeProvider.
when('/phones', {
template: '<phone-list></phone-list>'
}).
when('/phones/:phoneId', {
template: '<phone-detail></phone-detail>'
}).
otherwise('/phones');
}
]);
122 changes: 122 additions & 0 deletions app/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
body {
padding: 20px;
}

h1 {
border-bottom: 1px solid gray;
margin-top: 0;
}

.aria-status { margin-bottom: 10px; }
.aria-status-order{ margin-bottom: 5px; }

/* View: Phone list */
.phones {
list-style: none;
}

.phones li {
clear: both;
height: 115px;
padding-top: 15px;
}

.thumb {
float: left;
height: 100px;
margin: -0.5em 1em 1.5em 0;
padding-bottom: 1em;
width: 100px;
}

/* View: Phone detail */
.phone {
background-color: white;
display: none;
float: left;
height: 400px;
margin-bottom: 2em;
margin-right: 3em;
padding: 2em;
width: 400px;
}

.phone:first-child {
display: block;
}

.phone-images {
background-color: white;
float: left;
height: 450px;
overflow: hidden;
position: relative;
width: 450px;
}

.phone-thumbs {
list-style: none;
margin: 0;
}

.phone-thumbs img {
height: 100px;
padding: 1em;
width: 100px;
}

.phone-thumbs li {
background-color: white;
border: 1px solid black;
cursor: pointer;
display: inline-block;
margin: 1em;
}

.phone-thumbs img:focus {
border:2px solid black;
}

.phone-thumbs img:hover {
cursor: pointer;
}

.phone-thumbs button {
background: none;
border: 0;
}

.specs {
clear: both;
list-style: none;
margin: 0;
padding: 0;
}

.specs dt {
font-weight: bold;
}

.specs > li {
display: inline-block;
vertical-align: top;
width: 200px;
}

.specs > li > span {
font-size: 1.2em;
font-weight: bold;
}

ul.specs h2 { font-size:1.5em }

.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0,0,0,0);
border: 0;
}
10 changes: 10 additions & 0 deletions app/app.module.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
'use strict';

// Define the `phonecatApp` module
angular.module('phonecatApp', [
'ngAnimate',
'ngRoute',
'core',
'phoneDetail',
'phoneList'
]);
9 changes: 9 additions & 0 deletions app/core/checkmark/checkmark.filter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use strict';

angular.
module('core').
filter('checkmark', function() {
return function(input) {
return input ? '\u2713' : '\u2718';
};
});
14 changes: 14 additions & 0 deletions app/core/checkmark/checkmark.filter.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
'use strict';

describe('checkmark', function() {

beforeEach(module('core'));

it('should convert boolean values to unicode checkmark or cross',
inject(function(checkmarkFilter) {
expect(checkmarkFilter(true)).toBe('\u2713');
expect(checkmarkFilter(false)).toBe('\u2718');
})
);

});
4 changes: 4 additions & 0 deletions app/core/core.module.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
'use strict';

// Define the `core` module
angular.module('core', ['core.phone']);
4 changes: 4 additions & 0 deletions app/core/phone/phone.module.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
'use strict';

// Define the `core.phone` module
angular.module('core.phone', ['ngResource']);
15 changes: 15 additions & 0 deletions app/core/phone/phone.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use strict';

angular.
module('core.phone').
factory('Phone', ['$resource',
function($resource) {
return $resource('phones/:phoneId.json', {}, {
query: {
method: 'GET',
params: {phoneId: 'phones'},
isArray: true
}
});
}
]);
43 changes: 43 additions & 0 deletions app/core/phone/phone.service.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
'use strict';

describe('Phone', function() {
var $httpBackend;
var Phone;
var phonesData = [
{name: 'Phone X'},
{name: 'Phone Y'},
{name: 'Phone Z'}
];

// Add a custom equality tester before each test
beforeEach(function() {
jasmine.addCustomEqualityTester(angular.equals);
});

// Load the module that contains the `Phone` service before each test
beforeEach(module('core.phone'));

// Instantiate the service and "train" `$httpBackend` before each test
beforeEach(inject(function(_$httpBackend_, _Phone_) {
$httpBackend = _$httpBackend_;
$httpBackend.expectGET('phones/phones.json').respond(phonesData);

Phone = _Phone_;
}));

// Verify that there are no outstanding expectations or requests after each test
afterEach(function () {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});

it('should fetch the phones data from `/phones/phones.json`', function() {
var phones = Phone.query();

expect(phones).toEqual([]);

$httpBackend.flush();
expect(phones).toEqual(phonesData);
});

});
Loading