fix(radio): set the correct value for radio and radio group

also adds support for an empty selection with radio group
This commit is contained in:
Brandy Carney
2017-09-15 11:20:54 -04:00
parent 0dd53021b4
commit 08e12b580c
5 changed files with 194 additions and 207 deletions

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Ionic DateTime</title> <title>Ionic DateTime</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<script src="/dist/build/ionic.js"></script> <script src="/dist/ionic.js"></script>
</head> </head>
<body> <body>
<ion-app> <ion-app>

View File

@ -1,7 +1,10 @@
import { Component, Element, HostElement, Method, Prop, Listen } from '@stencil/core'; import { Component, Element, HostElement, Listen, Method, Prop, State } from '@stencil/core';
import { CssClassMap } from '../../index';
import { createThemedClasses } from '../../utils/theme'; import { createThemedClasses } from '../../utils/theme';
import { CssClassMap } from '../../index';
import { RadioEvent } from '../radio/radio';
@Component({ @Component({
tag: 'ion-item', tag: 'ion-item',
@ -14,10 +17,14 @@ import { createThemedClasses } from '../../utils/theme';
export class Item { export class Item {
private ids: number = -1; private ids: number = -1;
private id: string; private id: string;
private inputs: any = [];
private childStyles: CssClassMap = Object.create(null); private childStyles: CssClassMap = Object.create(null);
private label: any; private label: any;
// TODO get reorder from a parent list/group
@State() reorder: boolean = false;
@Element() private el: HTMLElement; @Element() private el: HTMLElement;
@Prop() mode: string; @Prop() mode: string;
@ -42,11 +49,25 @@ export class Item {
return hasChildStyleChange; return hasChildStyleChange;
} }
// TODO? this loads after radio group
// @Listen('ionRadioDidLoad')
// protected radioDidLoad(ev: RadioEvent) {
// const radio = ev.detail.radio;
// // register the input inside of the item
// // reset to the item's id instead of the radiogroup id
// radio.id = 'rb-' + this.registerInput('radio');
// radio.labelId = 'lbl-' + this.id;
// }
@Method() @Method()
getLabelText(): string { getLabelText(): string {
return this.label ? this.label.getText() : ''; return this.label ? this.label.getText() : '';
} }
ionViewWillLoad() {
this.id = (++itemId).toString();
}
ionViewDidLoad() { ionViewDidLoad() {
// Add item-button classes to each ion-button in the item // Add item-button classes to each ion-button in the item
const buttons = this.el.querySelectorAll('ion-button') as any; const buttons = this.el.querySelectorAll('ion-button') as any;
@ -64,14 +85,22 @@ export class Item {
// } // }
// this.viewLabel = false; // this.viewLabel = false;
// } // }
// if (this._viewLabel && this.inputs.length) {
// let labelText = this.getLabelText().trim();
// this._viewLabel = (labelText.length > 0);
// }
// if (this.inputs.length > 1) {
// this.setElementClass('item-multiple-inputs', true);
// }
} }
/** /**
* @hidden * @hidden
*/ */
@Method()
registerInput(type: string) { registerInput(type: string) {
// this.inputs.push(type); this.inputs.push(type);
return this.id + '-' + (++this.ids); return this.id + '-' + (++this.ids);
} }
@ -93,53 +122,18 @@ export class Item {
<slot></slot> <slot></slot>
</div> </div>
<slot name='end'></slot> <slot name='end'></slot>
{ this.reorder
? <ion-reorder></ion-reorder>
: null
}
</div> </div>
<div class="button-effect"></div>
</TagType> </TagType>
); );
// template:
// '<ng-content select="[slot="start"],ion-checkbox:not([slot="end"])"></ng-content>' +
// '<div class="item-inner">' +
// '<div class="input-wrapper">' +
// '<ng-content select="ion-label"></ng-content>' +
// '<ion-label *ngIf="_viewLabel">' +
// '<ng-content></ng-content>' +
// '</ion-label>' +
// '<ng-content select="ion-select,ion-input,ion-textarea,ion-datetime,ion-range,[item-content]"></ng-content>' +
// '</div>' +
// '<ng-content select="[slot="end"],ion-radio,ion-toggle"></ng-content>' +
// '<ion-reorder *ngIf="_hasReorder"></ion-reorder>' +
// '</div>' +
// '<div class="button-effect"></div>',
} }
// _ids: number = -1; // constructor() {
// _inputs: Array<string> = [];
// _label: Label;
// _viewLabel: boolean = true;
// _name: string = 'item';
// _hasReorder: boolean;
// /**
// * @hidden
// */
// id: string;
// /**
// * @hidden
// */
// labelId: string = null;
// constructor(
// form: Form,
// config: Config,
// elementRef: ElementRef,
// renderer: Renderer,
// @Optional() reorder: ItemReorder
// ) {
// super(config, elementRef, renderer, 'item');
// this._setName(elementRef); // this._setName(elementRef);
// this._hasReorder = !!reorder; // this._hasReorder = !!reorder;
// this.id = form.nextId().toString(); // this.id = form.nextId().toString();
@ -156,48 +150,6 @@ export class Item {
// } // }
// } // }
// /**
// * @hidden
// */
// registerInput(type: string) {
// this._inputs.push(type);
// return this.id + '-' + (++this._ids);
// }
// /**
// * @hidden
// */
// ngAfterContentInit() {
// if (this._viewLabel && this._inputs.length) {
// let labelText = this.getLabelText().trim();
// this._viewLabel = (labelText.length > 0);
// }
// if (this._inputs.length > 1) {
// this.setElementClass('item-multiple-inputs', true);
// }
// }
// /**
// * @hidden
// */
// _updateColor(newColor: string, componentName?: string) {
// componentName = componentName || 'item'; // item-radio
// this._setColor(newColor, componentName);
// }
// /**
// * @hidden
// */
// _setName(elementRef: ElementRef) {
// let nodeName = elementRef.nativeElement.nodeName.replace('ION-', '');
// if (nodeName === 'LIST-HEADER' || nodeName === 'ITEM-DIVIDER') {
// this._name = nodeName;
// }
// }
// /** // /**
// * @hidden // * @hidden
// */ // */
@ -223,18 +175,6 @@ export class Item {
// } // }
// } // }
// /**
// * @hidden
// */
// @ContentChildren(Button)
// set _buttons(buttons: QueryList<Button>) {
// buttons.forEach(button => {
// if (!button._size) {
// button.setElementClass('item-button', true);
// }
// });
// }
// /** // /**
// * @hidden // * @hidden
// */ // */
@ -245,3 +185,5 @@ export class Item {
// }); // });
// } // }
} }
var itemId = -1;

View File

@ -1,6 +1,8 @@
import { Component, Element, Event, EventEmitter, HostElement, Prop , State} from '@stencil/core'; import { Component, Element, Event, EventEmitter, HostElement, Listen, Prop, PropDidChange, State} from '@stencil/core';
import { isCheckedProperty } from '../../utils/helpers'; import { isCheckedProperty } from '../../utils/helpers';
import { Radio } from './radio';
import { Radio, RadioEvent } from './radio';
/** /**
@ -58,7 +60,9 @@ import { Radio } from './radio';
tag: 'ion-radio-group' tag: 'ion-radio-group'
}) })
export class RadioGroup { export class RadioGroup {
radios: Radio[]; radios: Radio[] = [];
id: number;
ids = 0;
@Element() el: HTMLElement; @Element() el: HTMLElement;
@ -86,32 +90,45 @@ export class RadioGroup {
*/ */
@Prop({ mutable: true }) value: string; @Prop({ mutable: true }) value: string;
@PropDidChange('value')
valueChanged() {
this.update();
this.ionChange.emit(this);
}
@Listen('ionRadioDidLoad')
protected radioDidLoad(ev: RadioEvent) {
const radio = ev.detail.radio;
this.radios.push(radio);
radio.id = 'rb-' + this.id + '-' + (++this.ids);
// if the value is not defined then use its unique id
radio.value = !radio.value ? radio.id : radio.value;
if (radio.checked && !this.value) {
this.value = radio.value;
}
}
@Listen('ionRadioDidToggle')
protected radioToggle(ev: RadioEvent) {
const radio = ev.detail.radio;
// If the group does not allow empty selection then checked
// should be true, otherwise leave it as is
radio.checked = this.allowEmptySelection ? radio.checked : true;
this.value = radio.checked ? radio.value : '';
}
ionViewWillLoad() { ionViewWillLoad() {
let radioGroupId = ++radioGroupIds; this.id = ++radioGroupIds;
let radioId = 0;
const radios = this.el.querySelectorAll('ion-radio') as NodeListOf<HostElement>;
for (var i = 0; i < radios.length; i++) {
const radio = radios[i].$instance;
if (radio) {
radio.id = 'rb-' + radioGroupId + '-' + (++radioId);
if (radio.checked) {
this.activeId = radio.id;
}
} else {
}
}
// Get the list header if it exists and set the id // Get the list header if it exists and set the id
const header = this.el.querySelector('ion-list-header') as HostElement; const header = this.el.querySelector('ion-list-header') as HostElement;
if (header) { if (header) {
if (!header.id) { if (!header.id) {
header.id = 'rg-hdr-' + radioGroupId; header.id = 'rg-hdr-' + this.id;
} }
this.headerId = header.id; this.headerId = header.id;
} }
@ -125,10 +142,9 @@ export class RadioGroup {
update() { update() {
// loop through each of the radios // loop through each of the radios
let hasChecked = false; let hasChecked = false;
this.radios.forEach(radio => { this.radios.forEach((radio: Radio) => {
// check this radiobutton if its value is // Check the radio if the value is the same as the group value
// the same as the radiogroups value
radio.checked = isCheckedProperty(this.value, radio.value) && !hasChecked; radio.checked = isCheckedProperty(this.value, radio.value) && !hasChecked;
if (radio.checked) { if (radio.checked) {

View File

@ -1,4 +1,5 @@
import { Component, CssClassMap, Element, Event, EventEmitter, Listen, Prop, PropDidChange, State } from '@stencil/core'; import { Component, CssClassMap, Element, Event, EventEmitter, Listen, Prop, PropDidChange, State } from '@stencil/core';
import { createThemedClasses } from '../../utils/theme'; import { createThemedClasses } from '../../utils/theme';
@ -51,7 +52,6 @@ import { createThemedClasses } from '../../utils/theme';
export class Radio { export class Radio {
mode: string; mode: string;
color: string; color: string;
group: any;
labelId: string; labelId: string;
styleTmr: any; styleTmr: any;
@ -61,6 +61,21 @@ export class Radio {
@State() activated: boolean; @State() activated: boolean;
/**
* @output {EventEmitter} Emitted when the radio loads.
*/
@Event() ionRadioDidLoad: EventEmitter;
/**
* @output {EventEmitter} Emitted when the radio unloads.
*/
@Event() ionRadioDidUnload: EventEmitter;
/**
* @output {EventEmitter} Emitted when the radio is toggled.
*/
@Event() ionRadioDidToggle: EventEmitter;
/** /**
* @output {EventEmitter} Emitted when the styles of the radio change. * @output {EventEmitter} Emitted when the styles of the radio change.
*/ */
@ -86,36 +101,17 @@ export class Radio {
*/ */
@Prop({ mutable: true }) value: string; @Prop({ mutable: true }) value: string;
ionViewWillLoad() { ionViewWillLoad() {
this.emitStyle(); this.emitStyle();
} }
ionViewDidLoad() { ionViewDidLoad() {
this.group = this.el.closest('ion-radio-group') as any; this.ionRadioDidLoad.emit({ radio: this });
const item = this.el.closest('ion-item') as any;
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;
}
// if the value is not defined then use it's unique id
this.value = !this.value ? this.id : this.value;
} }
@PropDidChange('checked') @PropDidChange('checked')
checkedChanged(val: boolean) { checkedChanged(val: boolean) {
this.ionSelect.emit({ checked: val }); this.ionSelect.emit({ checked: val });
if (this.group) {
this.group.value = this.value;
this.group.$instance.ionChange.emit(this);
}
this.emitStyle(); this.emitStyle();
} }
@ -145,6 +141,7 @@ export class Radio {
toggle() { toggle() {
this.checked = !this.checked; this.checked = !this.checked;
this.ionRadioDidToggle.emit({ radio: this });
} }
hostData() { hostData() {
@ -180,30 +177,8 @@ export class Radio {
} }
} }
// constructor() { export interface RadioEvent extends Event {
// if (_group) { detail: {
// // register with the radiogroup radio: Radio;
// this.id = 'rb-' + _group.add(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.setElementClass('item-radio', true);
// }
// }
// /**
// * @internal
// */
// ngOnInit() {
// if (this._group && isPresent(this._group.value)) {
// this.checked = isCheckedProperty(this._group.value, this.value);
// }
// if (this._group && this._group.disabled) {
// this.disabled = this._group.disabled;
// }
// }

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Ionic Radio</title> <title>Ionic Radio</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<script src="/dist/build/ionic.js"></script> <script src="/dist/ionic.js"></script>
</head> </head>
<body> <body>
<ion-app> <ion-app>
@ -13,47 +13,93 @@
<ion-title>Radios</ion-title> <ion-title>Radios</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content class="outer-content"> <ion-content class="radio-test outer-content">
<ion-radio-group> <ion-radio-group id="fruitRadio">
<ion-list> <ion-list>
<ion-list-header> <ion-list-header>
Fruits Fruits (Group w/ values)
</ion-list-header> </ion-list-header>
<ion-item> <ion-item>
<ion-label>Apple</ion-label> <ion-label>Apple</ion-label>
<ion-radio slot="start" name="apple" checked></ion-radio> <ion-radio slot="start" value="apple"></ion-radio>
<ion-icon slot="end" name="ionic"></ion-icon>
</ion-item> </ion-item>
<ion-item> <ion-item>
<ion-label>Banana</ion-label> <ion-label>Grape, checked, disabled</ion-label>
<ion-radio slot="start" name="banana" checked></ion-radio> <ion-radio slot="start" id="grapeChecked" value="grape" checked disabled></ion-radio>
</ion-item> </ion-item>
<ion-item> <ion-item>
<ion-label>Cherry</ion-label> <ion-label>Cherry</ion-label>
<ion-radio slot="start" color="danger" name="cherry"></ion-radio> <ion-radio slot="start" color="danger" value="cherry"></ion-radio>
</ion-item> </ion-item>
</ion-list> </ion-list>
</ion-radio-group> </ion-radio-group>
<ion-radio-group id="pizzaRadio">
<ion-list>
<ion-list-header>
Extra Pizza Topping (Group w/ no values)
</ion-list-header>
<ion-item>
<ion-label>Pepperoni</ion-label>
<ion-radio slot="end" name="pepperoni" checked></ion-radio>
</ion-item>
<ion-item>
<ion-label>Pineapple</ion-label>
<ion-radio slot="end" name="pineapple"></ion-radio>
</ion-item>
<ion-item>
<ion-label>Sausage</ion-label>
<ion-radio slot="end" color="danger" name="sausage"></ion-radio>
</ion-item>
</ion-list>
</ion-radio-group>
<ion-radio-group id="veggiesRadio" allow-empty-selection>
<ion-list>
<ion-list-header>
Veggies (Group w/ allow empty)
</ion-list-header>
<ion-item>
<ion-label>Carrot</ion-label>
<ion-radio slot="end" value="carrot"></ion-radio>
</ion-item>
<ion-item>
<ion-label>Tomato</ion-label>
<ion-radio slot="end" value="tomato"></ion-radio>
</ion-item>
<ion-item>
<ion-label>Broccoli</ion-label>
<ion-radio slot="end" color="danger" value="broccoli" checked></ion-radio>
</ion-item>
</ion-list>
</ion-radio-group>
<p aria-hidden="true" text-center>
<ion-button onClick="toggleBoolean('grapeChecked', 'checked')" outline small>Grape Checked</ion-button>
<ion-button onClick="toggleBoolean('grapeChecked', 'disabled')" outline small>Grape Disabled</ion-button>
<ion-button onClick="printRadioValues()" outline small>Print Values</ion-button>
</p>
<pre id="valuesCode"></pre>
<ion-list> <ion-list>
<ion-list-header> <ion-list-header>
Mo Fruits No Radio Group
</ion-list-header> </ion-list-header>
<ion-item>
<ion-label>Grape, checked, disabled</ion-label>
<ion-radio slot="start" id="grapeChecked" name="grape" checked disabled></ion-radio>
</ion-item>
<ion-item> <ion-item>
<ion-label>Kiwi, (ionChange) Secondary color</ion-label> <ion-label>Kiwi, (ionChange) Secondary color</ion-label>
<ion-radio slot="start" color="secondary" (ionChange)="kiwiChange($event)"></ion-radio> <ion-radio slot="start" color="secondary"></ion-radio>
</ion-item> </ion-item>
<ion-item> <ion-item>
<ion-label>Strawberry, (ionChange) checked="true"</ion-label> <ion-label>Strawberry, (ionChange) checked="true"</ion-label>
<ion-radio slot="start" color="light" (ionChange)="strawberryChange($event)" checked="true"></ion-radio> <ion-radio slot="start" color="light" checked="true"></ion-radio>
</ion-item> </ion-item>
<ion-item> <ion-item>
@ -73,29 +119,11 @@
</ion-list> </ion-list>
<p aria-hidden="true" text-center>
<ion-button onClick="toggleBoolean('grapeChecked', 'checked')" outline small>Grape Checked</ion-button>
<ion-button onClick="toggleBoolean('grapeChecked', 'disabled')" outline small>Grape Disabled</ion-button>
<ion-button onClick="printForm()" outline small>Print Form</ion-button>
</p>
<p> <p>
<ion-radio id="standAloneChecked"></ion-radio> <ion-radio id="standAloneChecked"></ion-radio>
Stand-alone checkbox: <span id="standAloneCheckedSpan"></span> Stand-alone checkbox: <span id="standAloneCheckedSpan"></span>
</p> </p>
<p aria-hidden="true" padding>
<code>appleCtrl.value: <span id="appleCtrlValue"></span></code><br>
<code>bananaCtrl.value: <span id="bananaCtrlValue"></span></code><br>
<code>cherry.value: <span id="cherryCtrlValue"></span></code><br>
<code>grape.value: <span id="grapeCtrlValue"></span></code><br>
<code>kiwiValue: <spa id="kiwiValue"></span></code><br>
<code>strawberryValue: <span id="strawberryValue">y</span></code><br>
</p>
<pre aria-hidden="true" padding id="formResults"></pre>
<ion-item> <ion-item>
<ion-label>Checkbox / Toggle</ion-label> <ion-label>Checkbox / Toggle</ion-label>
<ion-radio slot="start" id="checked"></ion-radio> <ion-radio slot="start" id="checked"></ion-radio>
@ -115,12 +143,30 @@
</ion-content> </ion-content>
<script> <script>
var radioValues = ['fruitRadio', 'pizzaRadio', 'veggiesRadio'];
printRadioValues();
function printForm(ev) { function printForm(ev) {
console.log('TODO get working with forms'); console.log('TODO get working with forms');
} }
function printRadioValues() {
var html = 'Values:<br>';
for (var i = 0; i < radioValues.length; i++) {
var radio = radioValues[i];
var ele = document.getElementById(radio);
html += '<span>' + radio + ': ' + ele.value + ' </span><br>';
}
var valueEle = document.getElementById('valuesCode');
valueEle.innerHTML = html;
}
function toggleBoolean(id, prop) { function toggleBoolean(id, prop) {
console.log('toggleBoolean', id);
var ele = document.getElementById(id); var ele = document.getElementById(id);
console.log('toggleBoolean', ele);
var isTrue = ele[prop] ? false : true; var isTrue = ele[prop] ? false : true;
ele[prop] = isTrue; ele[prop] = isTrue;
@ -128,6 +174,14 @@
} }
</script> </script>
<style>
.radio-test pre {
background: #f6f6f6;
border: 1px solid #ddd;
padding: 15px 10px;
}
</style>
</ion-app> </ion-app>
</body> </body>
</html> </html>