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

feat(ngAria): add support for ngReadonly #14140

Closed
wants to merge 1 commit into from
Closed
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
21 changes: 21 additions & 0 deletions docs/content/guide/accessibility.ngdoc
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Currently, ngAria interfaces with the following directives:
* {@link guide/accessibility#ngmodel ngModel}
* {@link guide/accessibility#ngdisabled ngDisabled}
* {@link guide/accessibility#ngrequired ngRequired}
* {@link guide/accessibility#ngReadonly ngReadonly}
* {@link guide/accessibility#ngvaluechecked ngChecked}
* {@link guide/accessibility#ngvaluechecked ngValue}
* {@link guide/accessibility#ngshow ngShow}
Expand All @@ -57,6 +58,7 @@ attributes (if they have not been explicitly specified by the developer):
* aria-valuenow
* aria-invalid
* aria-required
* aria-readonly

###Example

Expand Down Expand Up @@ -203,6 +205,25 @@ Becomes:
<md-checkbox ng-required="val" aria-required="true"></md-checkbox>
```

<h2 id="ngreadonly">ngReadonly</h2>

The boolean `readonly` attribute is only valid for native form controls such as `input` and
`textarea`. To properly indicate custom element directives such as `<md-checkbox>` or `<custom-input>`
as required, using ngAria with {@link ng.ngReadonly ngReadonly} will also add
`aria-readonly`. This tells accessibility APIs when a custom control is required.

###Example

```html
<md-checkbox ng-readonly="val"></md-checkbox>
```

Becomes:

```html
<md-checkbox ng-readonly="val" aria-readonly="true"></md-checkbox>
```

<h2 id="ngshow">ngShow</h2>

>The {@link ng.ngShow ngShow} directive shows or hides the
Expand Down
13 changes: 10 additions & 3 deletions src/ngAria/aria.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*
* For ngAria to do its magic, simply include the module `ngAria` as a dependency. The following
* directives are supported:
* `ngModel`, `ngChecked`, `ngRequired`, `ngValue`, `ngDisabled`, `ngShow`, `ngHide`, `ngClick`,
* `ngModel`, `ngChecked`, `ngReadonly`, `ngRequired`, `ngValue`, `ngDisabled`, `ngShow`, `ngHide`, `ngClick`,
* `ngDblClick`, and `ngMessages`.
*
* Below is a more detailed breakdown of the attributes handled by ngAria:
Expand All @@ -25,8 +25,9 @@
* |---------------------------------------------|----------------------------------------------------------------------------------------|
* | {@link ng.directive:ngModel ngModel} | aria-checked, aria-valuemin, aria-valuemax, aria-valuenow, aria-invalid, aria-required, input roles |
* | {@link ng.directive:ngDisabled ngDisabled} | aria-disabled |
* | {@link ng.directive:ngRequired ngRequired} | aria-required |
* | {@link ng.directive:ngChecked ngChecked} | aria-checked |
* | {@link ng.directive:ngRequired ngRequired} | aria-required
* | {@link ng.directive:ngChecked ngChecked} | aria-checked
* | {@link ng.directive:ngReadonly ngReadonly} | aria-readonly ||
* | {@link ng.directive:ngValue ngValue} | aria-checked |
* | {@link ng.directive:ngShow ngShow} | aria-hidden |
* | {@link ng.directive:ngHide ngHide} | aria-hidden |
Expand Down Expand Up @@ -91,6 +92,7 @@ function $AriaProvider() {
var config = {
ariaHidden: true,
ariaChecked: true,
ariaReadonly: true,
ariaDisabled: true,
ariaRequired: true,
ariaInvalid: true,
Expand All @@ -108,6 +110,7 @@ function $AriaProvider() {
*
* - **ariaHidden** – `{boolean}` – Enables/disables aria-hidden tags
* - **ariaChecked** – `{boolean}` – Enables/disables aria-checked tags
* - **ariaReadonly** – `{boolean}` – Enables/disables aria-readonly tags
* - **ariaDisabled** – `{boolean}` – Enables/disables aria-disabled tags
* - **ariaRequired** – `{boolean}` – Enables/disables aria-required tags
* - **ariaInvalid** – `{boolean}` – Enables/disables aria-invalid tags
Expand Down Expand Up @@ -170,6 +173,7 @@ function $AriaProvider() {
* The full list of directives that interface with ngAria:
* * **ngModel**
* * **ngChecked**
* * **ngReadonly**
* * **ngRequired**
* * **ngDisabled**
* * **ngValue**
Expand Down Expand Up @@ -209,6 +213,9 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
.directive('ngChecked', ['$aria', function($aria) {
return $aria.$$watchExpr('ngChecked', 'aria-checked', nodeBlackList, false);
}])
.directive('ngReadonly', ['$aria', function($aria) {
return $aria.$$watchExpr('ngReadonly', 'aria-readonly', nodeBlackList, false);
}])
.directive('ngRequired', ['$aria', function($aria) {
return $aria.$$watchExpr('ngRequired', 'aria-required', nodeBlackList, false);
}])
Expand Down
60 changes: 60 additions & 0 deletions test/ngAria/ariaSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,66 @@ describe('$aria', function() {
});
});

describe('aria-readonly', function() {
beforeEach(injectScopeAndCompiler);

they('should not attach itself to native $prop controls', {
input: '<input ng-readonly="val">',
textarea: '<textarea ng-readonly="val"></textarea>',
select: '<select ng-readonly="val"></select>',
button: '<button ng-readonly="val"></button>'
}, function(tmpl) {
var element = $compile(tmpl)(scope);
scope.$apply('val = true');

expect(element.attr('readonly')).toBeDefined();
expect(element.attr('aria-readonly')).toBeUndefined();
});

it('should attach itself to custom controls', function() {
compileElement('<div ng-readonly="val"></div>');
expect(element.attr('aria-readonly')).toBe('false');

scope.$apply('val = true');
expect(element.attr('aria-readonly')).toBe('true');

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: Redundant newline.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have removed the Nitpick: Redundant newline.

});

it('should not attach itself if an aria-readonly attribute is already present', function() {
compileElement('<div ng-readonly="val" aria-readonly="userSetValue"></div>');

expect(element.attr('aria-readonly')).toBe('userSetValue');
});

it('should always set aria-readonly to a boolean value', function() {
compileElement('<div ng-readonly="val"></div>');

scope.$apply('val = "test angular"');
expect(element.attr('aria-readonly')).toBe('true');

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

scope.$apply('val = {}');
expect(element.attr('aria-readonly')).toBe('true');
});
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also need some test for when ariaReadonly is disabled (e.g. similar to https://github.com/m-amr/angular.js/blob/add_ariaReadonly/test/ngAria/ariaSpec.js#L474).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added some tests when ariaReadonly is disabled


describe('aria-readonly when disabled', function() {
beforeEach(configAriaProvider({
ariaReadonly: false
}));
beforeEach(injectScopeAndCompiler);

it('should not add the aria-readonly attribute', function() {
compileElement("<input ng-model='val' readonly>");
expect(element.attr('aria-readonly')).toBeUndefined();

compileElement("<div ng-model='val' ng-readonly='true'></div>");
expect(element.attr('aria-readonly')).toBeUndefined();
});
});

describe('aria-required', function() {
beforeEach(injectScopeAndCompiler);

Expand Down