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

Commit 881c233

Browse files
committed
fix(ngAria): handle custom elements with role="checkbox"
1 parent 6a03ca2 commit 881c233

File tree

2 files changed

+93
-72
lines changed

2 files changed

+93
-72
lines changed

src/ngAria/aria.js

Lines changed: 84 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -211,88 +211,100 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
211211
restrict: 'A',
212212
require: '?ngModel',
213213
priority: 200, //Make sure watches are fired after any other directives that affect the ngModel value
214-
link: function(scope, elem, attr, ngModel) {
214+
compile: function(elem, attr) {
215215
var shape = getShape(attr, elem);
216-
var needsTabIndex = shouldAttachAttr('tabindex', 'tabindex', elem);
217-
218-
function ngAriaWatchModelValue() {
219-
return ngModel.$modelValue;
220-
}
221-
222-
function getRadioReaction() {
223-
if (needsTabIndex) {
224-
needsTabIndex = false;
225-
return function ngAriaRadioReaction(newVal) {
226-
var boolVal = (attr.value == ngModel.$viewValue);
227-
elem.attr('aria-checked', boolVal);
228-
elem.attr('tabindex', 0 - !boolVal);
229-
};
230-
} else {
231-
return function ngAriaRadioReaction(newVal) {
232-
elem.attr('aria-checked', (attr.value == ngModel.$viewValue));
233-
};
234-
}
235-
}
236-
237-
function ngAriaCheckboxReaction(newVal) {
238-
elem.attr('aria-checked', !ngModel.$isEmpty(ngModel.$viewValue));
239-
}
240216

241-
switch (shape) {
242-
case 'radio':
243-
case 'checkbox':
244-
if (shouldAttachRole(shape, elem)) {
245-
elem.attr('role', shape);
246-
}
247-
if (shouldAttachAttr('aria-checked', 'ariaChecked', elem)) {
248-
scope.$watch(ngAriaWatchModelValue, shape === 'radio' ?
249-
getRadioReaction() : ngAriaCheckboxReaction);
217+
return {
218+
pre: function(scope, elem, attr, ngModel) {
219+
if (shape === 'checkbox' && attr.role === 'checkbox') {
220+
ngModel.$isEmpty = function(value) {
221+
return value === false;
222+
};
250223
}
251-
break;
252-
case 'range':
253-
if (shouldAttachRole(shape, elem)) {
254-
elem.attr('role', 'slider');
224+
},
225+
post: function(scope, elem, attr, ngModel) {
226+
var needsTabIndex = shouldAttachAttr('tabindex', 'tabindex', elem);
227+
228+
function ngAriaWatchModelValue() {
229+
return ngModel.$modelValue;
255230
}
256-
if ($aria.config('ariaValue')) {
257-
if (attr.min && !elem.attr('aria-valuemin')) {
258-
elem.attr('aria-valuemin', attr.min);
259-
}
260-
if (attr.max && !elem.attr('aria-valuemax')) {
261-
elem.attr('aria-valuemax', attr.max);
262-
}
263-
if (!elem.attr('aria-valuenow')) {
264-
scope.$watch(ngAriaWatchModelValue, function ngAriaValueNowReaction(newVal) {
265-
elem.attr('aria-valuenow', newVal);
266-
});
231+
232+
function getRadioReaction() {
233+
if (needsTabIndex) {
234+
needsTabIndex = false;
235+
return function ngAriaRadioReaction(newVal) {
236+
var boolVal = (attr.value == ngModel.$viewValue);
237+
elem.attr('aria-checked', boolVal);
238+
elem.attr('tabindex', 0 - !boolVal);
239+
};
240+
} else {
241+
return function ngAriaRadioReaction(newVal) {
242+
elem.attr('aria-checked', (attr.value == ngModel.$viewValue));
243+
};
267244
}
268245
}
269-
break;
270-
case 'multiline':
271-
if (shouldAttachAttr('aria-multiline', 'ariaMultiline', elem)) {
272-
elem.attr('aria-multiline', true);
246+
247+
function ngAriaCheckboxReaction() {
248+
elem.attr('aria-checked', !ngModel.$isEmpty(ngModel.$viewValue));
273249
}
274-
break;
275-
}
276250

277-
if (needsTabIndex) {
278-
elem.attr('tabindex', 0);
279-
}
251+
switch (shape) {
252+
case 'radio':
253+
case 'checkbox':
254+
if (shouldAttachRole(shape, elem)) {
255+
elem.attr('role', shape);
256+
}
257+
if (shouldAttachAttr('aria-checked', 'ariaChecked', elem)) {
258+
scope.$watch(ngAriaWatchModelValue, shape === 'radio' ?
259+
getRadioReaction() : ngAriaCheckboxReaction);
260+
}
261+
break;
262+
case 'range':
263+
if (shouldAttachRole(shape, elem)) {
264+
elem.attr('role', 'slider');
265+
}
266+
if ($aria.config('ariaValue')) {
267+
if (attr.min && !elem.attr('aria-valuemin')) {
268+
elem.attr('aria-valuemin', attr.min);
269+
}
270+
if (attr.max && !elem.attr('aria-valuemax')) {
271+
elem.attr('aria-valuemax', attr.max);
272+
}
273+
if (!elem.attr('aria-valuenow')) {
274+
scope.$watch(ngAriaWatchModelValue, function ngAriaValueNowReaction(newVal) {
275+
elem.attr('aria-valuenow', newVal);
276+
});
277+
}
278+
}
279+
break;
280+
case 'multiline':
281+
if (shouldAttachAttr('aria-multiline', 'ariaMultiline', elem)) {
282+
elem.attr('aria-multiline', true);
283+
}
284+
break;
285+
}
280286

281-
if (ngModel.$validators.required && shouldAttachAttr('aria-required', 'ariaRequired', elem)) {
282-
scope.$watch(function ngAriaRequiredWatch() {
283-
return ngModel.$error.required;
284-
}, function ngAriaRequiredReaction(newVal) {
285-
elem.attr('aria-required', !!newVal);
286-
});
287-
}
287+
if (needsTabIndex) {
288+
elem.attr('tabindex', 0);
289+
}
288290

289-
if (shouldAttachAttr('aria-invalid', 'ariaInvalid', elem)) {
290-
scope.$watch(function ngAriaInvalidWatch() {
291-
return ngModel.$invalid;
292-
}, function ngAriaInvalidReaction(newVal) {
293-
elem.attr('aria-invalid', !!newVal);
294-
});
295-
}
291+
if (ngModel.$validators.required && shouldAttachAttr('aria-required', 'ariaRequired', elem)) {
292+
scope.$watch(function ngAriaRequiredWatch() {
293+
return ngModel.$error.required;
294+
}, function ngAriaRequiredReaction(newVal) {
295+
elem.attr('aria-required', !!newVal);
296+
});
297+
}
298+
299+
if (shouldAttachAttr('aria-invalid', 'ariaInvalid', elem)) {
300+
scope.$watch(function ngAriaInvalidWatch() {
301+
return ngModel.$invalid;
302+
}, function ngAriaInvalidReaction(newVal) {
303+
elem.attr('aria-invalid', !!newVal);
304+
});
305+
}
306+
}
307+
};
296308
}
297309
};
298310
}])

test/ngAria/ariaSpec.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,15 @@ describe('$aria', function() {
111111
expect(element.eq(0).attr('aria-checked')).toBe('false');
112112
});
113113

114+
it('should handle custom elements with role="checkbox"', function() {
115+
var element = $compile('<div role="checkbox" ng-model="checked"></div>')(scope);
116+
scope.$apply('checked = false');
117+
expect(element.eq(0).attr('aria-checked')).toBe('false');
118+
119+
scope.$apply('checked = true');
120+
expect(element.eq(0).attr('aria-checked')).toBe('true');
121+
});
122+
114123
it('should attach itself to input type="radio"', function() {
115124
var element = $compile('<input type="radio" ng-model="val" value="one">' +
116125
'<input type="radio" ng-model="val" value="two">')(scope);

0 commit comments

Comments
 (0)