diff --git a/src/demo-app/radio/radio-demo.html b/src/demo-app/radio/radio-demo.html index 6d0b02e458d..043690f4b7e 100644 --- a/src/demo-app/radio/radio-demo.html +++ b/src/demo-app/radio/radio-demo.html @@ -21,10 +21,20 @@ Disable buttons +
+ isRequired: {{isRequired}} + +
Align end
- + Option 1 Option 2 Option 3 diff --git a/src/demo-app/radio/radio-demo.ts b/src/demo-app/radio/radio-demo.ts index c967c1914ea..9f586b7cf38 100644 --- a/src/demo-app/radio/radio-demo.ts +++ b/src/demo-app/radio/radio-demo.ts @@ -8,8 +8,9 @@ import {Component} from '@angular/core'; styleUrls: ['radio-demo.css'], }) export class RadioDemo { - isDisabled: boolean = false; isAlignEnd: boolean = false; + isDisabled: boolean = false; + isRequired: boolean = false; favoriteSeason: string = 'Autumn'; seasonOptions = [ 'Winter', diff --git a/src/lib/radio/radio.html b/src/lib/radio/radio.html index 234a1728239..2b455fd4151 100644 --- a/src/lib/radio/radio.html +++ b/src/lib/radio/radio.html @@ -16,6 +16,7 @@ [checked]="checked" [disabled]="disabled" [name]="name" + [required]="required" [attr.aria-label]="ariaLabel" [attr.aria-labelledby]="ariaLabelledby" (change)="_onInputChange($event)" diff --git a/src/lib/radio/radio.spec.ts b/src/lib/radio/radio.spec.ts index 281594f1aba..c06f760be31 100644 --- a/src/lib/radio/radio.spec.ts +++ b/src/lib/radio/radio.spec.ts @@ -62,7 +62,7 @@ describe('MdRadio', () => { it('should set individual radio names based on the group name', () => { expect(groupInstance.name).toBeTruthy(); - for (let radio of radioInstances) { + for (const radio of radioInstances) { expect(radio.name).toBe(groupInstance.name); } }); @@ -92,14 +92,14 @@ describe('MdRadio', () => { testComponent.labelPos = 'before'; fixture.detectChanges(); - for (let radio of radioInstances) { + for (const radio of radioInstances) { expect(radio.labelPosition).toBe('before'); } testComponent.labelPos = 'after'; fixture.detectChanges(); - for (let radio of radioInstances) { + for (const radio of radioInstances) { expect(radio.labelPosition).toBe('after'); } }); @@ -108,11 +108,20 @@ describe('MdRadio', () => { testComponent.isGroupDisabled = true; fixture.detectChanges(); - for (let radio of radioInstances) { + for (const radio of radioInstances) { expect(radio.disabled).toBe(true); } }); + it('should set required to each radio button when the group is required', () => { + testComponent.isGroupRequired = true; + fixture.detectChanges(); + + for (const radio of radioInstances) { + expect(radio.required).toBe(true); + } + }); + it('should update the group value when one of the radios changes', () => { expect(groupInstance.value).toBeFalsy(); @@ -155,7 +164,7 @@ describe('MdRadio', () => { it('should emit a change event from radio buttons', () => { expect(radioInstances[0].checked).toBe(false); - let spies = radioInstances + const spies = radioInstances .map((radio, index) => jasmine.createSpy(`onChangeSpy ${index} for ${radio.name}`)); spies.forEach((spy, index) => radioInstances[index].change.subscribe(spy)); @@ -178,7 +187,7 @@ describe('MdRadio', () => { programmatically`, () => { expect(groupInstance.value).toBeFalsy(); - let changeSpy = jasmine.createSpy('radio-group change listener'); + const changeSpy = jasmine.createSpy('radio-group change listener'); groupInstance.change.subscribe(changeSpy); radioLabelElements[0].click(); @@ -265,7 +274,7 @@ describe('MdRadio', () => { testComponent.disableRipple = true; fixture.detectChanges(); - for (let radioLabel of radioLabelElements) { + for (const radioLabel of radioLabelElements) { dispatchFakeEvent(radioLabel, 'mousedown'); dispatchFakeEvent(radioLabel, 'mouseup'); @@ -275,7 +284,7 @@ describe('MdRadio', () => { testComponent.disableRipple = false; fixture.detectChanges(); - for (let radioLabel of radioLabelElements) { + for (const radioLabel of radioLabelElements) { dispatchFakeEvent(radioLabel, 'mousedown'); dispatchFakeEvent(radioLabel, 'mouseup'); @@ -285,7 +294,7 @@ describe('MdRadio', () => { it(`should update the group's selected radio to null when unchecking that radio programmatically`, () => { - let changeSpy = jasmine.createSpy('radio-group change listener'); + const changeSpy = jasmine.createSpy('radio-group change listener'); groupInstance.change.subscribe(changeSpy); radioInstances[0].checked = true; @@ -305,7 +314,7 @@ describe('MdRadio', () => { }); it('should not fire a change event from the group when a radio checked state changes', () => { - let changeSpy = jasmine.createSpy('radio-group change listener'); + const changeSpy = jasmine.createSpy('radio-group change listener'); groupInstance.change.subscribe(changeSpy); radioInstances[0].checked = true; @@ -324,7 +333,7 @@ describe('MdRadio', () => { }); it(`should update checked status if changed value to radio group's value`, () => { - let changeSpy = jasmine.createSpy('radio-group change listener'); + const changeSpy = jasmine.createSpy('radio-group change listener'); groupInstance.change.subscribe(changeSpy); groupInstance.value = 'apple'; @@ -403,25 +412,25 @@ describe('MdRadio', () => { it('should set individual radio names based on the group name', () => { expect(groupInstance.name).toBeTruthy(); - for (let radio of radioInstances) { + for (const radio of radioInstances) { expect(radio.name).toBe(groupInstance.name); } groupInstance.name = 'new name'; - for (let radio of radioInstances) { + for (const radio of radioInstances) { expect(radio.name).toBe(groupInstance.name); } }); it('should check the corresponding radio button on group value change', () => { expect(groupInstance.value).toBeFalsy(); - for (let radio of radioInstances) { + for (const radio of radioInstances) { expect(radio.checked).toBeFalsy(); } groupInstance.value = 'vanilla'; - for (let radio of radioInstances) { + for (const radio of radioInstances) { expect(radio.checked).toBe(groupInstance.value === radio.value); } expect(groupInstance.selected!.value).toBe(groupInstance.value); @@ -539,12 +548,12 @@ describe('MdRadio', () => { .filter(debugEl => debugEl.componentInstance.name == 'fruit') .map(debugEl => debugEl.componentInstance); - let fruitRadioNativeElements = radioDebugElements + const fruitRadioNativeElements = radioDebugElements .filter(debugEl => debugEl.componentInstance.name == 'fruit') .map(debugEl => debugEl.nativeElement); fruitRadioNativeInputs = []; - for (let element of fruitRadioNativeElements) { + for (const element of fruitRadioNativeElements) { fruitRadioNativeInputs.push( element.querySelector('input')); } }); @@ -579,6 +588,14 @@ describe('MdRadio', () => { expect(weatherRadioInstances[2].checked).toBe(true); }); + it('should add required attribute to the underlying input element if defined', () => { + const radioInstance = seasonRadioInstances[0]; + radioInstance.required = true; + fixture.detectChanges(); + + expect(radioInstance.required).toBe(true); + }); + it('should add aria-label attribute to the underlying input element if defined', () => { expect(fruitRadioNativeInputs[0].getAttribute('aria-label')).toBe('Banana'); }); @@ -630,6 +647,7 @@ describe('MdRadio', () => { template: ` { }) class RadiosInsideRadioGroup { labelPos: 'before' | 'after'; - isGroupDisabled: boolean = false; isFirstDisabled: boolean = false; + isGroupDisabled: boolean = false; + isGroupRequired: boolean = false; groupValue: string | null = null; disableRipple: boolean = false; color: string | null; diff --git a/src/lib/radio/radio.ts b/src/lib/radio/radio.ts index b6d9cb60898..e2287764635 100644 --- a/src/lib/radio/radio.ts +++ b/src/lib/radio/radio.ts @@ -105,6 +105,9 @@ export class MdRadioGroup extends _MdRadioGroupMixinBase /** Whether the radio group is disabled. */ private _disabled: boolean = false; + /** Whether the radio group is required. */ + private _required: boolean = false; + /** The method to be called in order to update ngModel */ _controlValueAccessorChangeFn: (value: any) => void = () => {}; @@ -189,12 +192,20 @@ export class MdRadioGroup extends _MdRadioGroupMixinBase /** Whether the radio group is disabled */ @Input() - get disabled() { return this._disabled; } + get disabled(): boolean { return this._disabled; } set disabled(value) { this._disabled = coerceBooleanProperty(value); this._markRadiosForCheck(); } + /** Whether the radio group is required */ + @Input() + get required(): boolean { return this._required; } + set required(value: boolean) { + this._required = coerceBooleanProperty(value); + this._markRadiosForCheck(); + } + constructor(private _changeDetector: ChangeDetectorRef) { super(); } @@ -231,7 +242,7 @@ export class MdRadioGroup extends _MdRadioGroupMixinBase /** Updates the `selected` radio button from the internal _value state. */ private _updateSelectedRadioFromValue(): void { // If the value already matches the selected radio, do nothing. - let isAlreadySelected = this._selected != null && this._selected.value == this._value; + const isAlreadySelected = this._selected != null && this._selected.value == this._value; if (this._radios != null && !isAlreadySelected) { this._selected = null; @@ -247,7 +258,7 @@ export class MdRadioGroup extends _MdRadioGroupMixinBase /** Dispatch change event with current selection and group value. */ _emitChangeEvent(): void { if (this._isInitialized) { - let event = new MdRadioChange(); + const event = new MdRadioChange(); event.source = this._selected; event.value = this._value; this.change.emit(event); @@ -424,6 +435,15 @@ export class MdRadioButton extends _MdRadioButtonMixinBase this._disabled = coerceBooleanProperty(value); } + /** Whether the radio button is required. */ + @Input() + get required(): boolean { + return this._required || (this.radioGroup && this.radioGroup.required); + } + set required(value: boolean) { + this._required = coerceBooleanProperty(value); + } + /** * Event emitted when the checked state of this radio button changes. * Change events are only emitted when the value changes due to user interaction with @@ -443,6 +463,9 @@ export class MdRadioButton extends _MdRadioButtonMixinBase /** Whether this radio is disabled. */ private _disabled: boolean; + /** Whether this radio is required. */ + private _required: boolean; + /** Value assigned to this radio.*/ private _value: any = null; @@ -516,7 +539,7 @@ export class MdRadioButton extends _MdRadioButtonMixinBase /** Dispatch change event with current value. */ private _emitChangeEvent(): void { - let event = new MdRadioChange(); + const event = new MdRadioChange(); event.source = this; event.value = this._value; this.change.emit(event); @@ -547,7 +570,7 @@ export class MdRadioButton extends _MdRadioButtonMixinBase // emit its event object to the `change` output. event.stopPropagation(); - let groupValueChanged = this.radioGroup && this.value != this.radioGroup.value; + const groupValueChanged = this.radioGroup && this.value != this.radioGroup.value; this.checked = true; this._emitChangeEvent();