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 {Form} from '../../util/form';
import {isTrueProperty, isDefined} from '../../util/util';
import {isTrueProperty, isDefined, isBlank} from '../../util/util';
import {Item} from '../item/item';
import {ListHeader} from '../list/list';
import {RadioGroup} from './radio-group';
@ -50,10 +50,10 @@ export class RadioButton {
private _checked: any = false;
private _disabled: any = false;
private _labelId: string;
private _value = null;
id: string;
@Input() value: string = '';
@Output() select: EventEmitter<RadioButton> = new EventEmitter();
constructor(
@ -63,21 +63,28 @@ export class RadioButton {
) {
_form.register(this);
if (_group) {
// register with the radiogroup
this.id = 'rb-' + _group.register(this);
}
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._labelId = 'lbl-' + _item.id;
this._item.setCssClass('item-radio', true);
}
console.log(this.value);
if (_group) {
_group.register(this);
}
}
check() {
this.checked = true;
@Input()
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()
@ -85,11 +92,28 @@ export class RadioButton {
return this._checked;
}
set checked(val) {
set checked(isChecked) {
if (!this._disabled) {
this._checked = isTrueProperty(val);
this.select.emit(this);
this._item && this._item.setCssClass('item-radio-checked', this._checked);
// only check/uncheck if it's not disabled
// 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);
}
/**
* @private
*/
setChecked(val: boolean) {
this._checked = isTrueProperty(val);
this._item && this._item.setCssClass('item-radio-checked', val);
}
/**
* @private
*/
@ -119,7 +135,7 @@ export class RadioButton {
console.debug('radio, select', this.id);
ev.preventDefault();
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 {RadioButton} from './radio-button';
@ -60,6 +60,8 @@ import {isDefined} from '../../util/util';
})
export class RadioGroup {
private _buttons: Array<RadioButton> = [];
private _ids: number = -1;
private _init: boolean = false;
id;
value;
@ -84,43 +86,67 @@ export class RadioGroup {
* the checked value.
* https://github.com/angular/angular/blob/master/modules/angular2/src/forms/directives/shared.ts#L34
*/
writeValue(value) {
this.value = isDefined(value) ? value : '';
this._buttons.forEach(button => {
let isChecked = button.checked;
button.setChecked(isChecked);
if (isChecked) {
this._renderer.setElementAttribute(this._elementRef.nativeElement, 'aria-activedescendant', button.id);
writeValue(val) {
if (val !== null) {
let oldVal = this.value;
// set the radiogroup's value
this.value = val || '';
this.updateValue();
// only emit change when it...changed
if (this.value !== oldVal && this._init) {
this.change.emit(this.value);
}
});
}
register(button: RadioButton) {
this._buttons.push(button);
button.select.subscribe(() => {
this.writeValue(button.value);
this.onChange(button.value);
this.change.emit(this);
});
this._init = true;
}
}
ngAfterContentInit() {
this._buttons.forEach(button => {
if (isDefined(this.value)) {
let isChecked = (button.value === this.value) || button.checked;
button.setChecked(isChecked);
if (isChecked) {
this.writeValue(button.value);
//this.onChange(button.value);
this._renderer.setElementAttribute(this._elementRef.nativeElement, 'aria-activedescendant', button.id);
}
}
// in a setTimeout to prevent
// Expression '_checked in RadioButton@0:24' has changed after
// it was checked. Previous value: 'true'. Current value: 'false'
// should be available in future versions of ng2
setTimeout(() => {
this.updateValue();
});
}
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)
private set _header(header) {
if (header) {
@ -134,16 +160,12 @@ export class RadioGroup {
/**
* @private
*/
onChange(val) {
// TODO: figure the whys and the becauses
}
onChange(val) {}
/**
* @private
*/
onTouched(val) {
// TODO: figure the whys and the becauses
}
onTouched(val) {}
/**
* @private

View File

@ -13,6 +13,12 @@ $radio-ios-icon-border-style: solid !default;
$radio-ios-disabled-opacity: 0.4 !default;
ion-radio {
position: relative;
display: inline-block;
}
// iOS Radio Circle: Unchecked
// -----------------------------------------
@ -56,6 +62,7 @@ $radio-ios-disabled-opacity: 0.4 !default;
// -----------------------------------------
.item ion-radio {
position: static;
display: block;
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;
ion-radio {
position: relative;
display: inline-block;
}
// Material Design Radio Outer Circle: Unchecked
// -----------------------------------------
@ -82,6 +88,7 @@ $radio-md-disabled-opacity: 0.4 !default;
// -----------------------------------------
.item ion-radio {
position: static;
display: block;
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.selectedCurrency = 'EUR';
this.relationship = 'friends';
this.relationship = 'enemies';
}
@ -52,4 +52,20 @@ class E2EApp {
console.log('Submitting form', this.fruitsForm.value);
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>
@ -78,20 +80,20 @@
<code><b>relationship:</b> {{relationship}}</code>
</div>
<ion-list radio-group>
<ion-item>
<ion-label>Dogs</ion-label>
<ion-radio checked></ion-radio>
</ion-item>
<ion-item>
<ion-label>Cats</ion-label>
<ion-radio></ion-radio>
</ion-item>
<ion-item>
<ion-label>Turtles</ion-label>
<ion-radio></ion-radio>
</ion-item>
</ion-list>
<div radio-group [(ngModel)]="pet" (change)="petChange($event)">
<p>
<ion-radio checked (select)="dogSelect($event)"></ion-radio>
Dogs
</p>
<p>
<ion-radio (select)="catSelect($event)"></ion-radio>
Cats
</p>
<p>
<ion-radio (select)="turtleSelect($event)"></ion-radio>
Turtles
</p>
</div>
<div padding>
<code><b>pet:</b> {{pet}}</code>