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
This commit is contained in:
Rafael Santana
2017-07-25 20:25:50 -03:00
committed by Andrew Seguin
parent 36f708c8dd
commit f06fe11103
5 changed files with 79 additions and 25 deletions
+11 -1
View File
@@ -21,10 +21,20 @@
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>
+2 -1
View File
@@ -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',
+1
View File
@@ -16,6 +16,7 @@
[checked]="checked"
[disabled]="disabled"
[name]="name"
[required]="required"
[attr.aria-label]="ariaLabel"
[attr.aria-labelledby]="ariaLabelledby"
(change)="_onInputChange($event)"
+37 -18
View File
@@ -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(<HTMLElement> 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: `
<md-radio-group [disabled]="isGroupDisabled"
[labelPosition]="labelPos"
[required]="isGroupRequired"
[value]="groupValue"
name="test-name">
<md-radio-button value="fire" [disableRipple]="disableRipple" [disabled]="isFirstDisabled"
@@ -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;
+28 -5
View File
@@ -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();