Skip to content

Commit 5081ad8

Browse files
author
Subra
committed
feat(ngAria): New module to make a11y easier
Adds various aria attributes to the built in directives. This module currently hooks into ng-show/hide, input, textarea button as a basic level of support for a11y. I am using this as a base for adding more tags into the mix for form direction flow, making ng-repeat updates atomic but the tags here are the most basic ones. Closes angular#5486 and angular#1600
1 parent 11f5aee commit 5081ad8

File tree

4 files changed

+414
-4
lines changed

4 files changed

+414
-4
lines changed

Gruntfile.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,9 @@ module.exports = function(grunt) {
147147
},
148148
ngTouch: {
149149
files: { src: 'src/ngTouch/**/*.js' },
150+
},
151+
ngAria: {
152+
files: {src: 'src/ngAria/**/*.js'},
150153
}
151154
},
152155

@@ -214,6 +217,10 @@ module.exports = function(grunt) {
214217
dest: 'build/angular-cookies.js',
215218
src: util.wrap(files['angularModules']['ngCookies'], 'module')
216219
},
220+
aria: {
221+
dest: 'build/angular-aria.js',
222+
src: util.wrap(files['angularModules']['ngAria'], 'module')
223+
},
217224
"promises-aplus-adapter": {
218225
dest:'tmp/promises-aplus-adapter++.js',
219226
src:['src/ng/q.js','lib/promises-aplus/promises-aplus-test-adapter.js']
@@ -230,7 +237,8 @@ module.exports = function(grunt) {
230237
touch: 'build/angular-touch.js',
231238
resource: 'build/angular-resource.js',
232239
route: 'build/angular-route.js',
233-
sanitize: 'build/angular-sanitize.js'
240+
sanitize: 'build/angular-sanitize.js',
241+
aria: 'build/angular-aria.js'
234242
},
235243

236244

angularFiles.js

+9-3
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@ var angularFiles = {
106106
'src/ngTouch/directive/ngClick.js',
107107
'src/ngTouch/directive/ngSwipe.js'
108108
],
109+
'ngAria': [
110+
'src/ngAria/aria.js'
111+
]
109112
},
110113

111114
'angularScenario': [
@@ -139,7 +142,8 @@ var angularFiles = {
139142
'test/ngRoute/**/*.js',
140143
'test/ngSanitize/**/*.js',
141144
'test/ngMock/*.js',
142-
'test/ngTouch/**/*.js'
145+
'test/ngTouch/**/*.js',
146+
'test/ngAria/*.js'
143147
],
144148

145149
'karma': [
@@ -173,7 +177,8 @@ var angularFiles = {
173177
'test/ngRoute/**/*.js',
174178
'test/ngResource/*.js',
175179
'test/ngSanitize/**/*.js',
176-
'test/ngTouch/**/*.js'
180+
'test/ngTouch/**/*.js',
181+
'test/ngAria/*.js'
177182
],
178183

179184
'karmaJquery': [
@@ -201,7 +206,8 @@ angularFiles['angularSrcModules'] = [].concat(
201206
angularFiles['angularModules']['ngRoute'],
202207
angularFiles['angularModules']['ngSanitize'],
203208
angularFiles['angularModules']['ngMock'],
204-
angularFiles['angularModules']['ngTouch']
209+
angularFiles['angularModules']['ngTouch'],
210+
angularFiles['angularModules']['ngAria']
205211
);
206212

207213
if (exports) {

src/ngAria/aria.js

+163
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
'use strict';
2+
3+
/**
4+
* @ngdoc module
5+
* @name ngAria
6+
* @description
7+
*
8+
* The `ngAria` module provides support for to embded aria tags that convey state or semantic information
9+
* about the appliction in order to allow assistive technologies to convey appropriate information to
10+
* persons with disabilities.
11+
*
12+
* <div doc-module-components="ngAria"></div>
13+
*
14+
* # Usage
15+
* To enable the addition of the aria tags, just require the module into your application and the tags will
16+
* hook into your ng-show/ng-hide, input, textarea, button, select and ng-required directives and adds the
17+
* appropriate aria-tags.
18+
*
19+
* Currently, the following aria tags are implemented:
20+
*
21+
* + aria-hidded
22+
* + aria-checked
23+
* + aria-diabled
24+
* + aria-required
25+
* + aria-invalid
26+
*
27+
* You can disable individual aria tags by using the {@link ngAria.$ariaProvider#config config} method.
28+
*
29+
*
30+
*/
31+
32+
/* global -ngAriaModule */
33+
var ngAriaModule = angular.module('ngAria', ['ng']).
34+
provider('$aria', $AriaProvider);
35+
36+
/**
37+
* @ngdoc provider
38+
* @name $ariaProvider
39+
*
40+
* @description
41+
*
42+
* Used for configuring aria attributes.
43+
*
44+
* ## Dependencies
45+
* Requires the {@link ngAria `ngAria`} module to be installed.
46+
*/
47+
function $AriaProvider(){
48+
var config = {
49+
ariaHidden : true,
50+
ariaChecked: true,
51+
ariaDisabled: true,
52+
ariaRequired: true,
53+
ariaInvalid: true
54+
}
55+
56+
/**
57+
* @ngdoc method
58+
* @name $ariaProvider#config
59+
*
60+
* @param {object} config object to enable/disable specific aria tags
61+
*
62+
* - **ariaHidden** – `{boolean}` – Enables/disables aria-hidden tags
63+
* - **ariaChecked** – `{boolean}` – Enables/disables aria-checked tags
64+
* - **ariaDisabled** – `{boolean}` – Enables/disables aria-disabled tags
65+
* - **ariaRequired** – `{boolean}` – Enables/disables aria-required tags
66+
* - **ariaInvalid** – `{boolean}` – Enables/disables aria-invalid tags
67+
*
68+
* @description
69+
* Enables/disables various aria tags
70+
*/
71+
this.config = function(newConfig){
72+
config = angular.extend(config, newConfig);
73+
}
74+
75+
var convertCase = function(input){
76+
return input.replace(/[A-Z]/g, function(letter, pos){
77+
return (pos ? '-' : '') + letter.toLowerCase();
78+
});
79+
};
80+
81+
var watchAttr = function(attrName, ariaName){
82+
return function(scope, elem, attr){
83+
if(config[ariaName]){
84+
var destroyWatcher = scope.$watch(function(){
85+
return elem.attr(attrName);
86+
}, function(newVal){
87+
elem.attr(convertCase(ariaName), !angular.isUndefined(newVal));
88+
});
89+
scope.$on('$destroy', function(){
90+
destroyWatcher();
91+
});
92+
}
93+
}
94+
}
95+
96+
var watchClass = function(className, ariaName){
97+
return function(scope, elem, attr){
98+
if(config[ariaName]){
99+
var destroyWatcher = scope.$watch(function(){
100+
return elem.attr('class');
101+
}, function(){
102+
elem.attr(convertCase(ariaName), elem.hasClass(className));
103+
});
104+
scope.$on('$destroy', function(){
105+
destroyWatcher();
106+
});
107+
}
108+
}
109+
}
110+
111+
this.$get = function(){
112+
return {
113+
ariaHidden: watchClass('ng-hide', 'ariaHidden'),
114+
ariaChecked: watchAttr('checked', 'ariaChecked'),
115+
ariaDisabled: watchAttr('disabled', 'ariaDisabled'),
116+
ariaRequired: watchAttr('required', 'ariaRequired'),
117+
ariaInvalid: watchClass('ng-invalid', 'ariaInvalid')
118+
}
119+
}
120+
}
121+
122+
ngAriaModule.directive('ngShow', ['$aria', function($aria){
123+
return $aria.ariaHidden;
124+
}]).directive('ngHide', ['$aria', function($aria){
125+
return $aria.ariaHidden;
126+
}]).directive('input', ['$aria', function($aria){
127+
return{
128+
restrict: 'E',
129+
link: function(scope, elem, attr){
130+
if(attr.type === 'checkbox'){
131+
$aria.ariaChecked(scope, elem, attr);
132+
}
133+
$aria.ariaDisabled(scope, elem, attr);
134+
$aria.ariaRequired(scope, elem, attr);
135+
$aria.ariaInvalid(scope, elem, attr);
136+
}
137+
};
138+
}]).directive('textarea', ['$aria', function($aria){
139+
return{
140+
restrict: 'E',
141+
link: function(scope, elem, attr){
142+
$aria.ariaDisabled(scope, elem, attr);
143+
$aria.ariaRequired(scope, elem, attr);
144+
}
145+
};
146+
}]).directive('button', ['$aria', function($aria){
147+
return{
148+
restrict: 'E',
149+
link: function(scope, elem, attr){
150+
$aria.ariaDisabled(scope, elem, attr);
151+
}
152+
};
153+
}]).directive('select', ['$aria', function($aria){
154+
return{
155+
restrict: 'E',
156+
link: function(scope, elem, attr){
157+
$aria.ariaDisabled(scope, elem, attr);
158+
$aria.ariaRequired(scope, elem, attr);
159+
}
160+
};
161+
}]).directive('ngRequired', ['$aria', function($aria){
162+
return $aria.ariaRequired;
163+
}]);

0 commit comments

Comments
 (0)