Skip to content

Commit f06fe11

Browse files
rafaelss95andrewseguin
authored andcommitted
feat(radio): add required attribute to radio-group (#5751)
* feat(radio): add required attribute to radio-group * chore(radio): add sample using required attribute in demo-app * feat(radio): add required attribute to radio-button * refactor(radio): change let to const when possible * style(radio): remove trailing white-spaces * docs: revert radio.md
1 parent 36f708c commit f06fe11

File tree

5 files changed

+79
-25
lines changed

5 files changed

+79
-25
lines changed

src/demo-app/radio/radio-demo.html

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,20 @@ <h1>Dynamic Example</h1>
2121
Disable buttons
2222
</button>
2323
</div>
24+
<div>
25+
<span>isRequired: {{isRequired}}</span>
26+
<button md-raised-button (click)="isRequired=!isRequired" class="demo-button">
27+
Require buttons
28+
</button>
29+
</div>
2430
<div>
2531
<span><md-checkbox [(ngModel)]="isAlignEnd">Align end</md-checkbox></span>
2632
</div>
27-
<md-radio-group name="my_options" [disabled]="isDisabled" [align]="isAlignEnd ? 'end' : 'start'">
33+
<md-radio-group
34+
name="my_options"
35+
[disabled]="isDisabled"
36+
[required]="isRequired"
37+
[align]="isAlignEnd ? 'end' : 'start'">
2838
<md-radio-button value="option_1">Option 1</md-radio-button>
2939
<md-radio-button value="option_2">Option 2</md-radio-button>
3040
<md-radio-button value="option_3">Option 3</md-radio-button>

src/demo-app/radio/radio-demo.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ import {Component} from '@angular/core';
88
styleUrls: ['radio-demo.css'],
99
})
1010
export class RadioDemo {
11-
isDisabled: boolean = false;
1211
isAlignEnd: boolean = false;
12+
isDisabled: boolean = false;
13+
isRequired: boolean = false;
1314
favoriteSeason: string = 'Autumn';
1415
seasonOptions = [
1516
'Winter',

src/lib/radio/radio.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
[checked]="checked"
1717
[disabled]="disabled"
1818
[name]="name"
19+
[required]="required"
1920
[attr.aria-label]="ariaLabel"
2021
[attr.aria-labelledby]="ariaLabelledby"
2122
(change)="_onInputChange($event)"

src/lib/radio/radio.spec.ts

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ describe('MdRadio', () => {
6262

6363
it('should set individual radio names based on the group name', () => {
6464
expect(groupInstance.name).toBeTruthy();
65-
for (let radio of radioInstances) {
65+
for (const radio of radioInstances) {
6666
expect(radio.name).toBe(groupInstance.name);
6767
}
6868
});
@@ -92,14 +92,14 @@ describe('MdRadio', () => {
9292
testComponent.labelPos = 'before';
9393
fixture.detectChanges();
9494

95-
for (let radio of radioInstances) {
95+
for (const radio of radioInstances) {
9696
expect(radio.labelPosition).toBe('before');
9797
}
9898

9999
testComponent.labelPos = 'after';
100100
fixture.detectChanges();
101101

102-
for (let radio of radioInstances) {
102+
for (const radio of radioInstances) {
103103
expect(radio.labelPosition).toBe('after');
104104
}
105105
});
@@ -108,11 +108,20 @@ describe('MdRadio', () => {
108108
testComponent.isGroupDisabled = true;
109109
fixture.detectChanges();
110110

111-
for (let radio of radioInstances) {
111+
for (const radio of radioInstances) {
112112
expect(radio.disabled).toBe(true);
113113
}
114114
});
115115

116+
it('should set required to each radio button when the group is required', () => {
117+
testComponent.isGroupRequired = true;
118+
fixture.detectChanges();
119+
120+
for (const radio of radioInstances) {
121+
expect(radio.required).toBe(true);
122+
}
123+
});
124+
116125
it('should update the group value when one of the radios changes', () => {
117126
expect(groupInstance.value).toBeFalsy();
118127

@@ -155,7 +164,7 @@ describe('MdRadio', () => {
155164
it('should emit a change event from radio buttons', () => {
156165
expect(radioInstances[0].checked).toBe(false);
157166

158-
let spies = radioInstances
167+
const spies = radioInstances
159168
.map((radio, index) => jasmine.createSpy(`onChangeSpy ${index} for ${radio.name}`));
160169

161170
spies.forEach((spy, index) => radioInstances[index].change.subscribe(spy));
@@ -178,7 +187,7 @@ describe('MdRadio', () => {
178187
programmatically`, () => {
179188
expect(groupInstance.value).toBeFalsy();
180189

181-
let changeSpy = jasmine.createSpy('radio-group change listener');
190+
const changeSpy = jasmine.createSpy('radio-group change listener');
182191
groupInstance.change.subscribe(changeSpy);
183192

184193
radioLabelElements[0].click();
@@ -265,7 +274,7 @@ describe('MdRadio', () => {
265274
testComponent.disableRipple = true;
266275
fixture.detectChanges();
267276

268-
for (let radioLabel of radioLabelElements) {
277+
for (const radioLabel of radioLabelElements) {
269278
dispatchFakeEvent(radioLabel, 'mousedown');
270279
dispatchFakeEvent(radioLabel, 'mouseup');
271280

@@ -275,7 +284,7 @@ describe('MdRadio', () => {
275284
testComponent.disableRipple = false;
276285
fixture.detectChanges();
277286

278-
for (let radioLabel of radioLabelElements) {
287+
for (const radioLabel of radioLabelElements) {
279288
dispatchFakeEvent(radioLabel, 'mousedown');
280289
dispatchFakeEvent(radioLabel, 'mouseup');
281290

@@ -285,7 +294,7 @@ describe('MdRadio', () => {
285294

286295
it(`should update the group's selected radio to null when unchecking that radio
287296
programmatically`, () => {
288-
let changeSpy = jasmine.createSpy('radio-group change listener');
297+
const changeSpy = jasmine.createSpy('radio-group change listener');
289298
groupInstance.change.subscribe(changeSpy);
290299
radioInstances[0].checked = true;
291300

@@ -305,7 +314,7 @@ describe('MdRadio', () => {
305314
});
306315

307316
it('should not fire a change event from the group when a radio checked state changes', () => {
308-
let changeSpy = jasmine.createSpy('radio-group change listener');
317+
const changeSpy = jasmine.createSpy('radio-group change listener');
309318
groupInstance.change.subscribe(changeSpy);
310319
radioInstances[0].checked = true;
311320

@@ -324,7 +333,7 @@ describe('MdRadio', () => {
324333
});
325334

326335
it(`should update checked status if changed value to radio group's value`, () => {
327-
let changeSpy = jasmine.createSpy('radio-group change listener');
336+
const changeSpy = jasmine.createSpy('radio-group change listener');
328337
groupInstance.change.subscribe(changeSpy);
329338
groupInstance.value = 'apple';
330339

@@ -403,25 +412,25 @@ describe('MdRadio', () => {
403412

404413
it('should set individual radio names based on the group name', () => {
405414
expect(groupInstance.name).toBeTruthy();
406-
for (let radio of radioInstances) {
415+
for (const radio of radioInstances) {
407416
expect(radio.name).toBe(groupInstance.name);
408417
}
409418

410419
groupInstance.name = 'new name';
411420

412-
for (let radio of radioInstances) {
421+
for (const radio of radioInstances) {
413422
expect(radio.name).toBe(groupInstance.name);
414423
}
415424
});
416425

417426
it('should check the corresponding radio button on group value change', () => {
418427
expect(groupInstance.value).toBeFalsy();
419-
for (let radio of radioInstances) {
428+
for (const radio of radioInstances) {
420429
expect(radio.checked).toBeFalsy();
421430
}
422431

423432
groupInstance.value = 'vanilla';
424-
for (let radio of radioInstances) {
433+
for (const radio of radioInstances) {
425434
expect(radio.checked).toBe(groupInstance.value === radio.value);
426435
}
427436
expect(groupInstance.selected!.value).toBe(groupInstance.value);
@@ -539,12 +548,12 @@ describe('MdRadio', () => {
539548
.filter(debugEl => debugEl.componentInstance.name == 'fruit')
540549
.map(debugEl => debugEl.componentInstance);
541550

542-
let fruitRadioNativeElements = radioDebugElements
551+
const fruitRadioNativeElements = radioDebugElements
543552
.filter(debugEl => debugEl.componentInstance.name == 'fruit')
544553
.map(debugEl => debugEl.nativeElement);
545554

546555
fruitRadioNativeInputs = [];
547-
for (let element of fruitRadioNativeElements) {
556+
for (const element of fruitRadioNativeElements) {
548557
fruitRadioNativeInputs.push(<HTMLElement> element.querySelector('input'));
549558
}
550559
});
@@ -579,6 +588,14 @@ describe('MdRadio', () => {
579588
expect(weatherRadioInstances[2].checked).toBe(true);
580589
});
581590

591+
it('should add required attribute to the underlying input element if defined', () => {
592+
const radioInstance = seasonRadioInstances[0];
593+
radioInstance.required = true;
594+
fixture.detectChanges();
595+
596+
expect(radioInstance.required).toBe(true);
597+
});
598+
582599
it('should add aria-label attribute to the underlying input element if defined', () => {
583600
expect(fruitRadioNativeInputs[0].getAttribute('aria-label')).toBe('Banana');
584601
});
@@ -630,6 +647,7 @@ describe('MdRadio', () => {
630647
template: `
631648
<md-radio-group [disabled]="isGroupDisabled"
632649
[labelPosition]="labelPos"
650+
[required]="isGroupRequired"
633651
[value]="groupValue"
634652
name="test-name">
635653
<md-radio-button value="fire" [disableRipple]="disableRipple" [disabled]="isFirstDisabled"
@@ -647,8 +665,9 @@ describe('MdRadio', () => {
647665
})
648666
class RadiosInsideRadioGroup {
649667
labelPos: 'before' | 'after';
650-
isGroupDisabled: boolean = false;
651668
isFirstDisabled: boolean = false;
669+
isGroupDisabled: boolean = false;
670+
isGroupRequired: boolean = false;
652671
groupValue: string | null = null;
653672
disableRipple: boolean = false;
654673
color: string | null;

src/lib/radio/radio.ts

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ export class MdRadioGroup extends _MdRadioGroupMixinBase
105105
/** Whether the radio group is disabled. */
106106
private _disabled: boolean = false;
107107

108+
/** Whether the radio group is required. */
109+
private _required: boolean = false;
110+
108111
/** The method to be called in order to update ngModel */
109112
_controlValueAccessorChangeFn: (value: any) => void = () => {};
110113

@@ -189,12 +192,20 @@ export class MdRadioGroup extends _MdRadioGroupMixinBase
189192

190193
/** Whether the radio group is disabled */
191194
@Input()
192-
get disabled() { return this._disabled; }
195+
get disabled(): boolean { return this._disabled; }
193196
set disabled(value) {
194197
this._disabled = coerceBooleanProperty(value);
195198
this._markRadiosForCheck();
196199
}
197200

201+
/** Whether the radio group is required */
202+
@Input()
203+
get required(): boolean { return this._required; }
204+
set required(value: boolean) {
205+
this._required = coerceBooleanProperty(value);
206+
this._markRadiosForCheck();
207+
}
208+
198209
constructor(private _changeDetector: ChangeDetectorRef) {
199210
super();
200211
}
@@ -231,7 +242,7 @@ export class MdRadioGroup extends _MdRadioGroupMixinBase
231242
/** Updates the `selected` radio button from the internal _value state. */
232243
private _updateSelectedRadioFromValue(): void {
233244
// If the value already matches the selected radio, do nothing.
234-
let isAlreadySelected = this._selected != null && this._selected.value == this._value;
245+
const isAlreadySelected = this._selected != null && this._selected.value == this._value;
235246

236247
if (this._radios != null && !isAlreadySelected) {
237248
this._selected = null;
@@ -247,7 +258,7 @@ export class MdRadioGroup extends _MdRadioGroupMixinBase
247258
/** Dispatch change event with current selection and group value. */
248259
_emitChangeEvent(): void {
249260
if (this._isInitialized) {
250-
let event = new MdRadioChange();
261+
const event = new MdRadioChange();
251262
event.source = this._selected;
252263
event.value = this._value;
253264
this.change.emit(event);
@@ -424,6 +435,15 @@ export class MdRadioButton extends _MdRadioButtonMixinBase
424435
this._disabled = coerceBooleanProperty(value);
425436
}
426437

438+
/** Whether the radio button is required. */
439+
@Input()
440+
get required(): boolean {
441+
return this._required || (this.radioGroup && this.radioGroup.required);
442+
}
443+
set required(value: boolean) {
444+
this._required = coerceBooleanProperty(value);
445+
}
446+
427447
/**
428448
* Event emitted when the checked state of this radio button changes.
429449
* Change events are only emitted when the value changes due to user interaction with
@@ -443,6 +463,9 @@ export class MdRadioButton extends _MdRadioButtonMixinBase
443463
/** Whether this radio is disabled. */
444464
private _disabled: boolean;
445465

466+
/** Whether this radio is required. */
467+
private _required: boolean;
468+
446469
/** Value assigned to this radio.*/
447470
private _value: any = null;
448471

@@ -516,7 +539,7 @@ export class MdRadioButton extends _MdRadioButtonMixinBase
516539

517540
/** Dispatch change event with current value. */
518541
private _emitChangeEvent(): void {
519-
let event = new MdRadioChange();
542+
const event = new MdRadioChange();
520543
event.source = this;
521544
event.value = this._value;
522545
this.change.emit(event);
@@ -547,7 +570,7 @@ export class MdRadioButton extends _MdRadioButtonMixinBase
547570
// emit its event object to the `change` output.
548571
event.stopPropagation();
549572

550-
let groupValueChanged = this.radioGroup && this.value != this.radioGroup.value;
573+
const groupValueChanged = this.radioGroup && this.value != this.radioGroup.value;
551574
this.checked = true;
552575
this._emitChangeEvent();
553576

0 commit comments

Comments
 (0)