Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(radio): add required attribute to radio-group #5751

Merged
merged 6 commits into from
Jul 25, 2017
Merged
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
12 changes: 11 additions & 1 deletion src/demo-app/radio/radio-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,20 @@ <h1>Dynamic Example</h1>
Disable buttons
</button>
</div>
<div>
<span>isRequired: {{isRequired}}</span>
<button md-raised-button (click)="isRequired=!isRequired" class="demo-button">
Require buttons
</button>
</div>
<div>
<span><md-checkbox [(ngModel)]="isAlignEnd">Align end</md-checkbox></span>
</div>
<md-radio-group name="my_options" [disabled]="isDisabled" [align]="isAlignEnd ? 'end' : 'start'">
<md-radio-group
name="my_options"
[disabled]="isDisabled"
[required]="isRequired"
[align]="isAlignEnd ? 'end' : 'start'">
<md-radio-button value="option_1">Option 1</md-radio-button>
<md-radio-button value="option_2">Option 2</md-radio-button>
<md-radio-button value="option_3">Option 3</md-radio-button>
Expand Down
3 changes: 2 additions & 1 deletion src/demo-app/radio/radio-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
1 change: 1 addition & 0 deletions src/lib/radio/radio.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
[checked]="checked"
[disabled]="disabled"
[name]="name"
[required]="required"
[attr.aria-label]="ariaLabel"
[attr.aria-labelledby]="ariaLabelledby"
(change)="_onInputChange($event)"
Expand Down
55 changes: 37 additions & 18 deletions src/lib/radio/radio.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
});
Expand Down Expand Up @@ -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');
}
});
Expand All @@ -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();

Expand Down Expand Up @@ -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));
Expand All @@ -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();
Expand Down Expand Up @@ -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');

Expand All @@ -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');

Expand All @@ -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;

Expand All @@ -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;

Expand All @@ -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';

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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(<HTMLElement> element.querySelector('input'));
}
});
Expand Down Expand Up @@ -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');
});
Expand Down Expand Up @@ -630,6 +647,7 @@ describe('MdRadio', () => {
template: `
<md-radio-group [disabled]="isGroupDisabled"
[labelPosition]="labelPos"
[required]="isGroupRequired"
[value]="groupValue"
name="test-name">
<md-radio-button value="fire" [disableRipple]="disableRipple" [disabled]="isFirstDisabled"
Expand All @@ -647,8 +665,9 @@ describe('MdRadio', () => {
})
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;
Expand Down
33 changes: 28 additions & 5 deletions src/lib/radio/radio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () => {};

Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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
Expand All @@ -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;

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();

Expand Down