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

Commit 2bbf401

Browse files
crisbetokara
authored andcommitted
fix(radio-group): wrong aria-checked value on load when used with ng-value (#9790)
Fixes #9400.
1 parent 694e561 commit 2bbf401

File tree

2 files changed

+49
-43
lines changed

2 files changed

+49
-43
lines changed

src/components/radioButton/radio-button.js

Lines changed: 27 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ function mdRadioGroupDirective($mdUtil, $mdConstant, $mdTheming, $timeout) {
6262
function linkRadioGroup(scope, element, attr, ctrls) {
6363
element.addClass('_md'); // private md component indicator for styling
6464
$mdTheming(element);
65-
65+
6666
var rgCtrl = ctrls[0];
6767
var ngModelCtrl = ctrls[1] || $mdUtil.fakeNgModel();
6868

@@ -205,8 +205,6 @@ function mdRadioGroupDirective($mdUtil, $mdConstant, $mdTheming, $timeout) {
205205

206206
// Activate radioButton's click listener (triggerHandler won't create a real click event)
207207
angular.element(target).triggerHandler('click');
208-
209-
210208
}
211209
}
212210

@@ -272,10 +270,18 @@ function mdRadioButtonDirective($mdAria, $mdUtil, $mdTheming) {
272270
$mdTheming(element);
273271
configureAria(element, scope);
274272

275-
initialize();
273+
// ngAria overwrites the aria-checked inside a $watch for ngValue.
274+
// We should defer the initialization until all the watches have fired.
275+
// This can also be fixed by removing the `lastChecked` check, but that'll
276+
// cause more DOM manipulation on each digest.
277+
if (attr.ngValue) {
278+
$mdUtil.nextTick(initialize, false);
279+
} else {
280+
initialize();
281+
}
276282

277283
/**
278-
*
284+
* Initializes the component.
279285
*/
280286
function initialize() {
281287
if (!rgCtrl) {
@@ -293,7 +299,7 @@ function mdRadioButtonDirective($mdAria, $mdUtil, $mdTheming) {
293299
}
294300

295301
/**
296-
*
302+
* On click functionality.
297303
*/
298304
function listener(ev) {
299305
if (element[0].hasAttribute('disabled') || rgCtrl.isDisabled()) return;
@@ -308,58 +314,37 @@ function mdRadioButtonDirective($mdAria, $mdUtil, $mdTheming) {
308314
* Update the `aria-activedescendant` attribute.
309315
*/
310316
function render() {
311-
var checked = (rgCtrl.getViewValue() == attr.value);
312-
if (checked === lastChecked) {
313-
return;
314-
}
317+
var checked = rgCtrl.getViewValue() == attr.value;
315318

316-
lastChecked = checked;
317-
element.attr('aria-checked', checked);
319+
if (checked === lastChecked) return;
318320

319-
if (checked) {
320-
markParentAsChecked(true);
321-
element.addClass(CHECKED_CSS);
321+
if (element[0].parentNode.nodeName.toLowerCase() !== 'md-radio-group') {
322+
// If the radioButton is inside a div, then add class so highlighting will work
323+
element.parent().toggleClass(CHECKED_CSS, checked);
324+
}
322325

326+
if (checked) {
323327
rgCtrl.setActiveDescendant(element.attr('id'));
324-
325-
} else {
326-
markParentAsChecked(false);
327-
element.removeClass(CHECKED_CSS);
328328
}
329329

330-
/**
331-
* If the radioButton is inside a div, then add class so highlighting will work...
332-
*/
333-
function markParentAsChecked(addClass ) {
334-
if ( element.parent()[0].nodeName != "MD-RADIO-GROUP") {
335-
element.parent()[ !!addClass ? 'addClass' : 'removeClass'](CHECKED_CSS);
336-
}
330+
lastChecked = checked;
337331

338-
}
332+
element
333+
.attr('aria-checked', checked)
334+
.toggleClass(CHECKED_CSS, checked);
339335
}
340336

341337
/**
342338
* Inject ARIA-specific attributes appropriate for each radio button
343339
*/
344-
function configureAria( element, scope ){
345-
scope.ariaId = buildAriaID();
346-
340+
function configureAria(element, scope){
347341
element.attr({
348-
'id' : scope.ariaId,
349-
'role' : 'radio',
350-
'aria-checked' : 'false'
342+
id: attr.id || 'radio_' + $mdUtil.nextUid(),
343+
role: 'radio',
344+
'aria-checked': 'false'
351345
});
352346

353347
$mdAria.expectWithText(element, 'aria-label');
354-
355-
/**
356-
* Build a unique ID for each radio button that will be used with aria-activedescendant.
357-
* Preserve existing ID if already specified.
358-
* @returns {*|string}
359-
*/
360-
function buildAriaID() {
361-
return attr.id || ( 'radio' + "_" + $mdUtil.nextUid() );
362-
}
363348
}
364349
}
365350
}

src/components/radioButton/radio-button.spec.js

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ describe('mdRadioButton component', function() {
22

33
var CHECKED_CSS = 'md-checked';
44

5-
beforeEach(module('material.components.radioButton'));
5+
beforeEach(module('material.components.radioButton', 'ngAria'));
66

77
describe('md-radio-group', function() {
88

@@ -209,6 +209,27 @@ describe('mdRadioButton component', function() {
209209
expect(element[0]).toHaveClass('md-focused');
210210
}));
211211

212+
it('should apply aria-checked properly when using ng-value', inject(function($compile, $rootScope, $timeout) {
213+
$rootScope.color = 'blue';
214+
215+
var element = $compile(
216+
'<md-radio-group ng-model="color">' +
217+
'<md-radio-button ng-value="\'red\'"></md-radio-button>' +
218+
'<md-radio-button ng-value="\'blue\'"></md-radio-button>' +
219+
'<md-radio-button ng-value="\'green\'"></md-radio-button>' +
220+
'</md-radio-group>')
221+
($rootScope);
222+
223+
$timeout.flush();
224+
225+
var checkedItems = element[0].querySelectorAll('[aria-checked="true"]');
226+
var uncheckedItems = element[0].querySelectorAll('[aria-checked="false"]');
227+
228+
expect(checkedItems.length).toBe(1);
229+
expect(uncheckedItems.length).toBe(2);
230+
expect(checkedItems[0].getAttribute('value')).toBe($rootScope.color);
231+
}));
232+
212233
});
213234

214235
describe('md-radio-button', function() {

0 commit comments

Comments
 (0)