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

Commit

Permalink
fix(ngAria): handle elements with role="checkbox/menuitemcheckbox"
Browse files Browse the repository at this point in the history
Fixes #11317
Closes #11321
  • Loading branch information
Narretz authored and petebacondarwin committed Mar 17, 2015
1 parent 45f006f commit 1c282af
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 75 deletions.
157 changes: 85 additions & 72 deletions src/ngAria/aria.js
Expand Up @@ -211,88 +211,101 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
restrict: 'A',
require: '?ngModel',
priority: 200, //Make sure watches are fired after any other directives that affect the ngModel value
link: function(scope, elem, attr, ngModel) {
compile: function(elem, attr) {
var shape = getShape(attr, elem);
var needsTabIndex = shouldAttachAttr('tabindex', 'tabindex', elem);

function ngAriaWatchModelValue() {
return ngModel.$modelValue;
}

function getRadioReaction() {
if (needsTabIndex) {
needsTabIndex = false;
return function ngAriaRadioReaction(newVal) {
var boolVal = (attr.value == ngModel.$viewValue);
elem.attr('aria-checked', boolVal);
elem.attr('tabindex', 0 - !boolVal);
};
} else {
return function ngAriaRadioReaction(newVal) {
elem.attr('aria-checked', (attr.value == ngModel.$viewValue));
};
}
}

function ngAriaCheckboxReaction(newVal) {
elem.attr('aria-checked', !ngModel.$isEmpty(ngModel.$viewValue));
}

switch (shape) {
case 'radio':
case 'checkbox':
if (shouldAttachRole(shape, elem)) {
elem.attr('role', shape);
}
if (shouldAttachAttr('aria-checked', 'ariaChecked', elem)) {
scope.$watch(ngAriaWatchModelValue, shape === 'radio' ?
getRadioReaction() : ngAriaCheckboxReaction);
return {
pre: function(scope, elem, attr, ngModel) {
if (shape === 'checkbox' && attr.type !== 'checkbox') {
//Use the input[checkbox] $isEmpty implementation for elements with checkbox roles
ngModel.$isEmpty = function(value) {
return value === false;
};
}
break;
case 'range':
if (shouldAttachRole(shape, elem)) {
elem.attr('role', 'slider');
},
post: function(scope, elem, attr, ngModel) {
var needsTabIndex = shouldAttachAttr('tabindex', 'tabindex', elem);

function ngAriaWatchModelValue() {
return ngModel.$modelValue;
}
if ($aria.config('ariaValue')) {
if (attr.min && !elem.attr('aria-valuemin')) {
elem.attr('aria-valuemin', attr.min);
}
if (attr.max && !elem.attr('aria-valuemax')) {
elem.attr('aria-valuemax', attr.max);
}
if (!elem.attr('aria-valuenow')) {
scope.$watch(ngAriaWatchModelValue, function ngAriaValueNowReaction(newVal) {
elem.attr('aria-valuenow', newVal);
});

function getRadioReaction() {
if (needsTabIndex) {
needsTabIndex = false;
return function ngAriaRadioReaction(newVal) {
var boolVal = (attr.value == ngModel.$viewValue);
elem.attr('aria-checked', boolVal);
elem.attr('tabindex', 0 - !boolVal);
};
} else {
return function ngAriaRadioReaction(newVal) {
elem.attr('aria-checked', (attr.value == ngModel.$viewValue));
};
}
}
break;
case 'multiline':
if (shouldAttachAttr('aria-multiline', 'ariaMultiline', elem)) {
elem.attr('aria-multiline', true);

function ngAriaCheckboxReaction() {
elem.attr('aria-checked', !ngModel.$isEmpty(ngModel.$viewValue));
}
break;
}

if (needsTabIndex) {
elem.attr('tabindex', 0);
}
switch (shape) {
case 'radio':
case 'checkbox':
if (shouldAttachRole(shape, elem)) {
elem.attr('role', shape);
}
if (shouldAttachAttr('aria-checked', 'ariaChecked', elem)) {
scope.$watch(ngAriaWatchModelValue, shape === 'radio' ?
getRadioReaction() : ngAriaCheckboxReaction);
}
break;
case 'range':
if (shouldAttachRole(shape, elem)) {
elem.attr('role', 'slider');
}
if ($aria.config('ariaValue')) {
if (attr.min && !elem.attr('aria-valuemin')) {
elem.attr('aria-valuemin', attr.min);
}
if (attr.max && !elem.attr('aria-valuemax')) {
elem.attr('aria-valuemax', attr.max);
}
if (!elem.attr('aria-valuenow')) {
scope.$watch(ngAriaWatchModelValue, function ngAriaValueNowReaction(newVal) {
elem.attr('aria-valuenow', newVal);
});
}
}
break;
case 'multiline':
if (shouldAttachAttr('aria-multiline', 'ariaMultiline', elem)) {
elem.attr('aria-multiline', true);
}
break;
}

if (ngModel.$validators.required && shouldAttachAttr('aria-required', 'ariaRequired', elem)) {
scope.$watch(function ngAriaRequiredWatch() {
return ngModel.$error.required;
}, function ngAriaRequiredReaction(newVal) {
elem.attr('aria-required', !!newVal);
});
}
if (needsTabIndex) {
elem.attr('tabindex', 0);
}

if (shouldAttachAttr('aria-invalid', 'ariaInvalid', elem)) {
scope.$watch(function ngAriaInvalidWatch() {
return ngModel.$invalid;
}, function ngAriaInvalidReaction(newVal) {
elem.attr('aria-invalid', !!newVal);
});
}
if (ngModel.$validators.required && shouldAttachAttr('aria-required', 'ariaRequired', elem)) {
scope.$watch(function ngAriaRequiredWatch() {
return ngModel.$error.required;
}, function ngAriaRequiredReaction(newVal) {
elem.attr('aria-required', !!newVal);
});
}

if (shouldAttachAttr('aria-invalid', 'ariaInvalid', elem)) {
scope.$watch(function ngAriaInvalidWatch() {
return ngModel.$invalid;
}, function ngAriaInvalidReaction(newVal) {
elem.attr('aria-invalid', !!newVal);
});
}
}
};
}
};
}])
Expand Down
18 changes: 15 additions & 3 deletions test/ngAria/ariaSpec.js
Expand Up @@ -155,27 +155,39 @@ describe('$aria', function() {
});

it('should attach itself to role="radio"', function() {
scope.$apply("val = 'one'");
compileElement('<div role="radio" ng-model="val" value="{{val}}"></div>');
scope.val = 'one';
compileElement('<div role="radio" ng-model="val" value="one"></div>');
expect(element.attr('aria-checked')).toBe('true');

scope.$apply("val = 'two'");
expect(element.attr('aria-checked')).toBe('false');
});

it('should attach itself to role="checkbox"', function() {
scope.val = true;
compileElement('<div role="checkbox" ng-model="val"></div>');
expect(element.attr('aria-checked')).toBe('true');

scope.$apply('val = false');
expect(element.attr('aria-checked')).toBe('false');
});

it('should attach itself to role="menuitemradio"', function() {
scope.val = 'one';
compileElement('<div role="menuitemradio" ng-model="val" value="{{val}}"></div>');
compileElement('<div role="menuitemradio" ng-model="val" value="one"></div>');
expect(element.attr('aria-checked')).toBe('true');

scope.$apply("val = 'two'");
expect(element.attr('aria-checked')).toBe('false');
});

it('should attach itself to role="menuitemcheckbox"', function() {
scope.val = true;
compileElement('<div role="menuitemcheckbox" ng-model="val"></div>');
expect(element.attr('aria-checked')).toBe('true');

scope.$apply('val = false');
expect(element.attr('aria-checked')).toBe('false');
});

it('should not attach itself if an aria-checked value is already present', function() {
Expand Down

0 comments on commit 1c282af

Please sign in to comment.