fix(radio): prevent multiple radio buttons from being checked

This commit is contained in:
Adam Bradley
2016-01-26 22:43:55 -06:00
parent 6890532ab4
commit 334fb3c5b3
6 changed files with 145 additions and 75 deletions

View File

@ -1,7 +1,7 @@
import {Component, Optional, Input, Output, HostListener, EventEmitter} from 'angular2/core'; import {Component, Optional, Input, Output, HostListener, EventEmitter} from 'angular2/core';
import {Form} from '../../util/form'; import {Form} from '../../util/form';
import {isTrueProperty, isDefined} from '../../util/util'; import {isTrueProperty, isDefined, isBlank} from '../../util/util';
import {Item} from '../item/item'; import {Item} from '../item/item';
import {ListHeader} from '../list/list'; import {ListHeader} from '../list/list';
import {RadioGroup} from './radio-group'; import {RadioGroup} from './radio-group';
@ -50,10 +50,10 @@ export class RadioButton {
private _checked: any = false; private _checked: any = false;
private _disabled: any = false; private _disabled: any = false;
private _labelId: string; private _labelId: string;
private _value = null;
id: string; id: string;
@Input() value: string = '';
@Output() select: EventEmitter<RadioButton> = new EventEmitter(); @Output() select: EventEmitter<RadioButton> = new EventEmitter();
constructor( constructor(
@ -63,21 +63,28 @@ export class RadioButton {
) { ) {
_form.register(this); _form.register(this);
if (_group) {
// register with the radiogroup
this.id = 'rb-' + _group.register(this);
}
if (_item) { if (_item) {
// register the input inside of the item
// reset to the item's id instead of the radiogroup id
this.id = 'rb-' + _item.registerInput('radio'); this.id = 'rb-' + _item.registerInput('radio');
this._labelId = 'lbl-' + _item.id; this._labelId = 'lbl-' + _item.id;
this._item.setCssClass('item-radio', true); this._item.setCssClass('item-radio', true);
} }
console.log(this.value);
if (_group) {
_group.register(this);
}
} }
check() { @Input()
this.checked = true; get value() {
// if the value is not defined then use it's unique id
return isBlank(this._value) ? this.id : this._value;
}
set value(val) {
this._value = val;
} }
@Input() @Input()
@ -85,11 +92,28 @@ export class RadioButton {
return this._checked; return this._checked;
} }
set checked(val) { set checked(isChecked) {
if (!this._disabled) { if (!this._disabled) {
this._checked = isTrueProperty(val); // only check/uncheck if it's not disabled
this.select.emit(this);
this._item && this._item.setCssClass('item-radio-checked', this._checked); // emit the select event for the radiogroup to catch
this._checked = isTrueProperty(isChecked);
this.select.emit(this.value);
// if it's a stand-alone radiobutton nothing else happens
// if it was within a radiogroup then updateAsChecked will
// get called again
this.updateAsChecked(this._checked);
}
}
/**
* @private
*/
updateAsChecked(val: boolean) {
this._checked = val;
if (this._item) {
this._item.setCssClass('item-radio-checked', val);
} }
} }
@ -103,14 +127,6 @@ export class RadioButton {
this._item && this._item.setCssClass('item-radio-disabled', this._disabled); this._item && this._item.setCssClass('item-radio-disabled', this._disabled);
} }
/**
* @private
*/
setChecked(val: boolean) {
this._checked = isTrueProperty(val);
this._item && this._item.setCssClass('item-radio-checked', val);
}
/** /**
* @private * @private
*/ */
@ -119,7 +135,7 @@ export class RadioButton {
console.debug('radio, select', this.id); console.debug('radio, select', this.id);
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
this.check(); this.checked = true;
} }
/** /**

View File

@ -1,4 +1,4 @@
import {Directive, ElementRef, Renderer, Optional, Input, Output, ContentChild, EventEmitter} from 'angular2/core'; import {Directive, ElementRef, Renderer, Optional, Input, Output, HostListener, ContentChild, EventEmitter} from 'angular2/core';
import {NgControl} from 'angular2/common'; import {NgControl} from 'angular2/common';
import {RadioButton} from './radio-button'; import {RadioButton} from './radio-button';
@ -60,6 +60,8 @@ import {isDefined} from '../../util/util';
}) })
export class RadioGroup { export class RadioGroup {
private _buttons: Array<RadioButton> = []; private _buttons: Array<RadioButton> = [];
private _ids: number = -1;
private _init: boolean = false;
id; id;
value; value;
@ -84,43 +86,67 @@ export class RadioGroup {
* the checked value. * the checked value.
* https://github.com/angular/angular/blob/master/modules/angular2/src/forms/directives/shared.ts#L34 * https://github.com/angular/angular/blob/master/modules/angular2/src/forms/directives/shared.ts#L34
*/ */
writeValue(value) { writeValue(val) {
this.value = isDefined(value) ? value : ''; if (val !== null) {
this._buttons.forEach(button => { let oldVal = this.value;
let isChecked = button.checked;
button.setChecked(isChecked); // set the radiogroup's value
if (isChecked) { this.value = val || '';
this._renderer.setElementAttribute(this._elementRef.nativeElement, 'aria-activedescendant', button.id);
} this.updateValue();
});
// only emit change when it...changed
if (this.value !== oldVal && this._init) {
this.change.emit(this.value);
} }
register(button: RadioButton) { this._init = true;
this._buttons.push(button); }
button.select.subscribe(() => {
this.writeValue(button.value);
this.onChange(button.value);
this.change.emit(this);
});
} }
ngAfterContentInit() { ngAfterContentInit() {
this._buttons.forEach(button => { // in a setTimeout to prevent
// Expression '_checked in RadioButton@0:24' has changed after
if (isDefined(this.value)) { // it was checked. Previous value: 'true'. Current value: 'false'
let isChecked = (button.value === this.value) || button.checked; // should be available in future versions of ng2
button.setChecked(isChecked); setTimeout(() => {
if (isChecked) { this.updateValue();
this.writeValue(button.value);
//this.onChange(button.value);
this._renderer.setElementAttribute(this._elementRef.nativeElement, 'aria-activedescendant', button.id);
}
}
}); });
} }
private updateValue() {
if (isDefined(this.value)) {
// loop through each of the radiobuttons
this._buttons.forEach(radioButton => {
// check this radiobutton if its value is
// the same as the radiogroups value
let isChecked = (radioButton.value === this.value);
radioButton.updateAsChecked(isChecked);
if (isChecked) {
// if this button is checked, then set it as
// the radiogroup's active descendant
this._renderer.setElementAttribute(this._elementRef.nativeElement, 'aria-activedescendant', radioButton.id);
}
});
}
}
register(button: RadioButton): string {
this._buttons.push(button);
// listen for radiobutton select events
button.select.subscribe(() => {
// this radiobutton has been selected
this.writeValue(button.value);
this.onChange(button.value);
});
return this.id + '-' + (++this._ids);
}
@ContentChild(ListHeader) @ContentChild(ListHeader)
private set _header(header) { private set _header(header) {
if (header) { if (header) {
@ -134,16 +160,12 @@ export class RadioGroup {
/** /**
* @private * @private
*/ */
onChange(val) { onChange(val) {}
// TODO: figure the whys and the becauses
}
/** /**
* @private * @private
*/ */
onTouched(val) { onTouched(val) {}
// TODO: figure the whys and the becauses
}
/** /**
* @private * @private

View File

@ -13,6 +13,12 @@ $radio-ios-icon-border-style: solid !default;
$radio-ios-disabled-opacity: 0.4 !default; $radio-ios-disabled-opacity: 0.4 !default;
ion-radio {
position: relative;
display: inline-block;
}
// iOS Radio Circle: Unchecked // iOS Radio Circle: Unchecked
// ----------------------------------------- // -----------------------------------------
@ -56,6 +62,7 @@ $radio-ios-disabled-opacity: 0.4 !default;
// ----------------------------------------- // -----------------------------------------
.item ion-radio { .item ion-radio {
position: static;
display: block; display: block;
margin: $item-ios-padding-media-top ($item-ios-padding-right / 2) $item-ios-padding-media-bottom ($item-ios-padding-left / 2); margin: $item-ios-padding-media-top ($item-ios-padding-right / 2) $item-ios-padding-media-bottom ($item-ios-padding-left / 2);
} }

View File

@ -18,6 +18,12 @@ $radio-md-transition-easing: cubic-bezier(.4, 0, .2, 1) !default;
$radio-md-disabled-opacity: 0.4 !default; $radio-md-disabled-opacity: 0.4 !default;
ion-radio {
position: relative;
display: inline-block;
}
// Material Design Radio Outer Circle: Unchecked // Material Design Radio Outer Circle: Unchecked
// ----------------------------------------- // -----------------------------------------
@ -82,6 +88,7 @@ $radio-md-disabled-opacity: 0.4 !default;
// ----------------------------------------- // -----------------------------------------
.item ion-radio { .item ion-radio {
position: static;
display: block; display: block;
margin: $item-md-padding-media-top ($item-md-padding-right / 2) $item-md-padding-media-bottom 0; margin: $item-md-padding-media-top ($item-md-padding-right / 2) $item-md-padding-media-bottom 0;
} }

View File

@ -32,7 +32,7 @@ class E2EApp {
this.currencies = ['USD', 'EUR']; this.currencies = ['USD', 'EUR'];
this.selectedCurrency = 'EUR'; this.selectedCurrency = 'EUR';
this.relationship = 'friends'; this.relationship = 'enemies';
} }
@ -52,4 +52,20 @@ class E2EApp {
console.log('Submitting form', this.fruitsForm.value); console.log('Submitting form', this.fruitsForm.value);
event.preventDefault(); event.preventDefault();
} }
petChange(ev) {
console.log('petChange', ev);
}
dogSelect(ev) {
console.log('dogSelect', ev);
}
catSelect(ev) {
console.log('catSelect', ev);
}
turtleSelect(ev) {
console.log('turtleSelect', ev);
}
} }

View File

@ -1,5 +1,7 @@
<ion-toolbar><ion-title>Radio Group</ion-title></ion-toolbar> <ion-toolbar>
<ion-title>Radio Group</ion-title>
</ion-toolbar>
<ion-content> <ion-content>
@ -78,20 +80,20 @@
<code><b>relationship:</b> {{relationship}}</code> <code><b>relationship:</b> {{relationship}}</code>
</div> </div>
<ion-list radio-group> <div radio-group [(ngModel)]="pet" (change)="petChange($event)">
<ion-item> <p>
<ion-label>Dogs</ion-label> <ion-radio checked (select)="dogSelect($event)"></ion-radio>
<ion-radio checked></ion-radio> Dogs
</ion-item> </p>
<ion-item> <p>
<ion-label>Cats</ion-label> <ion-radio (select)="catSelect($event)"></ion-radio>
<ion-radio></ion-radio> Cats
</ion-item> </p>
<ion-item> <p>
<ion-label>Turtles</ion-label> <ion-radio (select)="turtleSelect($event)"></ion-radio>
<ion-radio></ion-radio> Turtles
</ion-item> </p>
</ion-list> </div>
<div padding> <div padding>
<code><b>pet:</b> {{pet}}</code> <code><b>pet:</b> {{pet}}</code>