mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-19 03:32:21 +08:00
checkbox and radio
This commit is contained in:
@ -17,8 +17,9 @@ import {Icon} from '../icon/icon';
|
|||||||
@IonicComponent({
|
@IonicComponent({
|
||||||
selector: 'ion-checkbox',
|
selector: 'ion-checkbox',
|
||||||
host: {
|
host: {
|
||||||
'class': 'item',
|
'[class.item]': 'item',
|
||||||
'[attr.aria-checked]': 'checked'
|
'[attr.aria-checked]': 'input.checked',
|
||||||
|
'(^click)': 'onClick($event)'
|
||||||
},
|
},
|
||||||
defaultProperties: {
|
defaultProperties: {
|
||||||
'iconOff': 'ion-ios-circle-outline',
|
'iconOff': 'ion-ios-circle-outline',
|
||||||
@ -27,7 +28,7 @@ import {Icon} from '../icon/icon';
|
|||||||
})
|
})
|
||||||
@IonicView({
|
@IonicView({
|
||||||
template:
|
template:
|
||||||
'<div class="item-media media-checkbox" (click)="onClick($event)">' +
|
'<div class="item-media media-checkbox">' +
|
||||||
'<icon [name]="iconOff" class="checkbox-off"></icon>' +
|
'<icon [name]="iconOff" class="checkbox-off"></icon>' +
|
||||||
'<icon [name]="iconOn" class="checkbox-on"></icon>' +
|
'<icon [name]="iconOn" class="checkbox-on"></icon>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
@ -36,42 +37,31 @@ import {Icon} from '../icon/icon';
|
|||||||
'</div>'
|
'</div>'
|
||||||
})
|
})
|
||||||
export class Checkbox extends IonInputItem {
|
export class Checkbox extends IonInputItem {
|
||||||
|
|
||||||
_checkbox: CheckboxInput;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
elementRef: ElementRef,
|
elementRef: ElementRef,
|
||||||
config: IonicConfig
|
config: IonicConfig
|
||||||
) {
|
) {
|
||||||
super(elementRef, config);
|
super(elementRef, config);
|
||||||
|
this.item = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
onAllChangesDone() {
|
onClick(ev) {
|
||||||
// Enforce that this directive actually contains a checkbox
|
// toggling with spacebar fires mouse event
|
||||||
if (this._checkbox == null) {
|
if (ev.target.tagName === "INPUT") return;
|
||||||
throw 'No <input type="checkbox"> found inside of <ion-checkbox>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
registerInput(checkboxDir) {
|
this.input.checked = !this.input.checked;
|
||||||
if (this._checkbox != null) {
|
|
||||||
throw 'Only one <input type="checkbox"> is allowed per <ion-checkbox>'
|
|
||||||
}
|
|
||||||
this._checkbox = checkboxDir.elementRef.nativeElement;
|
|
||||||
this._checkboxDir = checkboxDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
onClick(e) {
|
//TODO trigger change/mouse event on the actual input to trigger
|
||||||
let val = !this._checkbox.checked;
|
|
||||||
this._checkbox.checked = val;
|
|
||||||
this.checked = val;
|
|
||||||
|
|
||||||
//TODO figure out a way to trigger change on the actual input to trigger
|
|
||||||
// form updates
|
// form updates
|
||||||
|
|
||||||
// this._checkbox.dispatchEvent(e);
|
// this._checkbox.dispatchEvent(e);
|
||||||
//this._checkboxDir.control.valueAccessor.writeValue(val);
|
//this._checkboxDir.control.valueAccessor.writeValue(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onChangeEvent(input) {
|
||||||
|
//TODO can we just assume this will always be true?
|
||||||
|
this.input.checked = this.input.elementRef.nativeElement.checked;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
import {Component, View} from 'angular2/angular2';
|
import {App} from 'ionic/ionic';
|
||||||
|
|
||||||
import {Query, QueryList, NgFor} from 'angular2/angular2';
|
|
||||||
// import {FormBuilder, Validators, NgFormControl, Control, NgControlName, NgControlGroup, ControlContainer} from 'angular2/forms';
|
|
||||||
import {
|
import {
|
||||||
Control,
|
Control,
|
||||||
ControlGroup,
|
ControlGroup,
|
||||||
@ -14,8 +11,6 @@ import {
|
|||||||
NgFormModel,
|
NgFormModel,
|
||||||
FormBuilder
|
FormBuilder
|
||||||
} from 'angular2/forms';
|
} from 'angular2/forms';
|
||||||
import {App, Checkbox, Content, List} from 'ionic/ionic';
|
|
||||||
//import {IONIC_DIRECTIVES} from 'ionic/ionic'
|
|
||||||
|
|
||||||
@App({
|
@App({
|
||||||
templateUrl: 'main.html'
|
templateUrl: 'main.html'
|
||||||
|
@ -9,11 +9,17 @@ import {RadioButton} from '../radio/radio';
|
|||||||
|
|
||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: 'input[type=checkbox],input[type=radio]'
|
selector: 'input[type=checkbox],input[type=radio]',
|
||||||
|
properties: [ 'checked', 'name' ],
|
||||||
|
host: {
|
||||||
|
'[checked]': 'checked',
|
||||||
|
'[attr.name]': 'name',
|
||||||
|
'(change)': 'onChangeEvent($event)'
|
||||||
|
}
|
||||||
})
|
})
|
||||||
export class TapInput extends IonInput {
|
export class TapInput extends IonInput {
|
||||||
constructor(
|
constructor(
|
||||||
@Optional() @Parent() checkboxContainer: Checkbox, //TODO have this be either Checkbox or Radio
|
@Optional() @Parent() checkboxContainer: Checkbox,
|
||||||
@Optional() @Parent() radioContainer: RadioButton,
|
@Optional() @Parent() radioContainer: RadioButton,
|
||||||
@Optional() @Ancestor() scrollView: Content,
|
@Optional() @Ancestor() scrollView: Content,
|
||||||
@Attribute('type') type: string,
|
@Attribute('type') type: string,
|
||||||
@ -35,4 +41,8 @@ export class TapInput extends IonInput {
|
|||||||
this.tabIndex = this.tabIndex || '';
|
this.tabIndex = this.tabIndex || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onChangeEvent(ev) {
|
||||||
|
this.container && this.container.onChangeEvent(this);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,134 +1,105 @@
|
|||||||
import {ElementRef, Ancestor} from 'angular2/angular2';
|
import {ElementRef, Ancestor} from 'angular2/angular2';
|
||||||
|
|
||||||
import {IonicDirective, IonicComponent, IonicView} from '../../config/annotations';
|
import {IonicDirective, IonicComponent, IonicView} from '../../config/annotations';
|
||||||
|
import {IonicConfig} from '../../config/config';
|
||||||
|
import {Ion} from '../ion';
|
||||||
|
import {IonInputItem} from '../form/form';
|
||||||
|
|
||||||
|
let groupName = -1;
|
||||||
|
|
||||||
@IonicDirective({
|
@IonicDirective({
|
||||||
selector: 'ion-radio-group'
|
selector: 'ion-radio-group',
|
||||||
|
host: {
|
||||||
|
'[class.list]': 'list'
|
||||||
|
}
|
||||||
})
|
})
|
||||||
export class RadioGroup {
|
export class RadioGroup extends Ion {
|
||||||
|
|
||||||
|
_name: number = ++groupName;
|
||||||
|
buttons: Array<RadioButton> = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
elementRef: ElementRef//,
|
elementRef: ElementRef,
|
||||||
//cd:ControlDirective
|
ionicConfig: IonicConfig
|
||||||
) {
|
) {
|
||||||
this.ele = elementRef.nativeElement
|
super(elementRef, ionicConfig);
|
||||||
// this.config = RadioGroup.config.invoke(this)
|
this.list = true;
|
||||||
// this.controlDirective = cd;
|
|
||||||
// cd.valueAccessor = this; //ControlDirective should inject CheckboxControlDirective
|
|
||||||
|
|
||||||
this.ele.classList.add('list');
|
|
||||||
|
|
||||||
this.buttons = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Much like ngModel, this is called from our valueAccessor for the attached
|
|
||||||
* ControlDirective to update the value internally.
|
|
||||||
*/
|
|
||||||
writeValue(value) {
|
|
||||||
this.value = value;
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
this.selectFromValue(value);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by child SegmentButtons to bind themselves to
|
|
||||||
* the Segment.
|
|
||||||
*/
|
|
||||||
register(radioButton) {
|
register(radioButton) {
|
||||||
this.buttons.push(radioButton);
|
this.buttons.push(radioButton);
|
||||||
|
let inputEl = radioButton.input.elementRef.nativeElement;
|
||||||
// If we don't have a default value, and this is the
|
if (!inputEl.hasAttribute('name')) {
|
||||||
// first button added, select it
|
radioButton.input.name = this._name;
|
||||||
if(!this.value && this.buttons.length === 1) {
|
|
||||||
setTimeout(() => {
|
|
||||||
// We need to defer so the control directive can initialize
|
|
||||||
this.selected(radioButton);
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
// if (radioButton && !radioButton.hasAttribute('name')){
|
||||||
|
// radioButton.setAttribute('name', this._name);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
update(input) {
|
||||||
* Select the button with the given value.
|
|
||||||
*/
|
|
||||||
selectFromValue(value) {
|
|
||||||
for (let button of this.buttons) {
|
for (let button of this.buttons) {
|
||||||
if(button.value === value) {
|
button.input.checked = button.input.elementRef.nativeElement.checked;
|
||||||
this.selected(button);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicate a button should be selected.
|
|
||||||
*/
|
|
||||||
selected(radioButton) {
|
|
||||||
for(let button of this.buttons) {
|
|
||||||
button.setActive(false);
|
|
||||||
}
|
|
||||||
radioButton.setActive(true);
|
|
||||||
|
|
||||||
this.value = radioButton.value;
|
|
||||||
// TODO: Better way to do this?
|
|
||||||
this.controlDirective._control().updateValue(this.value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@IonicComponent({
|
@IonicComponent({
|
||||||
selector: 'ion-radio',
|
selector: 'ion-radio',
|
||||||
properties: [
|
|
||||||
'value'
|
|
||||||
],
|
|
||||||
host: {
|
host: {
|
||||||
'(^click)': 'buttonClicked($event)'
|
'[class.item]': 'item',
|
||||||
|
'[class.active]': 'input.checked',
|
||||||
|
'[attr.aria-checked]': 'input.checked',
|
||||||
|
'(^click)': 'onClick($event)'
|
||||||
|
},
|
||||||
|
defaultProperties: {
|
||||||
|
'iconOff': 'ion-ios-circle-outline',
|
||||||
|
'iconOn': 'ion-ios-checkmark'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@IonicView({
|
@IonicView({
|
||||||
template: `
|
template:
|
||||||
<div class="item-content">
|
'<div class="item-media media-radio">' +
|
||||||
<div class="item-title">
|
'<icon [name]="iconOff" class="radio-off"></icon>' +
|
||||||
<content></content>
|
'<icon [name]="iconOn" class="radio-on"></icon>' +
|
||||||
</div>
|
'</div>' +
|
||||||
<div class="item-media media-radio">
|
'<div class="item-content">' +
|
||||||
<icon class="radio-off"></icon>
|
'<content></content>' +
|
||||||
<icon class="ion-ios-checkmark-empty radio-on"></icon>
|
'</div>'
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
})
|
})
|
||||||
export class RadioButton {
|
export class RadioButton extends IonInputItem {
|
||||||
constructor(
|
constructor(
|
||||||
@Ancestor() group: RadioGroup,
|
@Ancestor() group: RadioGroup,
|
||||||
elementRef: ElementRef
|
elementRef: ElementRef,
|
||||||
|
config: IonicConfig
|
||||||
) {
|
) {
|
||||||
this.ele = elementRef.ele;
|
super(elementRef, config);
|
||||||
|
this.item = true;
|
||||||
this.ele.classList.add('item')
|
|
||||||
this.ele.setAttribute('aria-checked', true)
|
|
||||||
|
|
||||||
this.group = group;
|
this.group = group;
|
||||||
|
|
||||||
group.register(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setActive(isActive) {
|
registerInput(input) {
|
||||||
// TODO: No ele
|
this.input = input;
|
||||||
if(isActive) {
|
this.group.register(this);
|
||||||
this.ele.classList.add('active');
|
|
||||||
this.ele.setAttribute('aria-checked', true)
|
|
||||||
} else {
|
|
||||||
this.ele.classList.remove('active');
|
|
||||||
this.ele.setAttribute('aria-checked', false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buttonClicked(event) {
|
onClick(ev) {
|
||||||
this.group.selected(this, event);
|
// switching between radio buttons with arrow keys fires a MouseEvent
|
||||||
event.preventDefault();
|
if (ev.target.tagName === "INPUT") return;
|
||||||
|
this.input.checked = !this.input.checked;
|
||||||
|
|
||||||
|
//let bindings update first
|
||||||
|
setTimeout(() => this.group.update(this.input));
|
||||||
|
|
||||||
|
//TODO figure out a way to trigger change on the actual input to trigger
|
||||||
|
// form updates
|
||||||
|
|
||||||
|
// this._checkbox.dispatchEvent(e);
|
||||||
|
//this._checkboxDir.control.valueAccessor.writeValue(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onChangeEvent(input) {
|
||||||
|
this.group.update(input);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,16 @@
|
|||||||
import {App} from 'ionic/ionic';
|
import {App} from 'ionic/ionic';
|
||||||
|
import {
|
||||||
|
Control,
|
||||||
|
ControlGroup,
|
||||||
|
NgForm,
|
||||||
|
formDirectives,
|
||||||
|
Validators,
|
||||||
|
NgControl,
|
||||||
|
ControlValueAccessor,
|
||||||
|
NgControlName,
|
||||||
|
NgFormModel,
|
||||||
|
FormBuilder
|
||||||
|
} from 'angular2/forms';
|
||||||
|
|
||||||
|
|
||||||
@App({
|
@App({
|
||||||
@ -6,9 +18,33 @@ import {App} from 'ionic/ionic';
|
|||||||
})
|
})
|
||||||
class IonicApp {
|
class IonicApp {
|
||||||
constructor() {
|
constructor() {
|
||||||
// var fb = new FormBuilder();
|
this.fruitsGroup1 = new ControlGroup({
|
||||||
// this.form = fb.group({
|
"appleCtrl": new Control("", isChecked),
|
||||||
// preferredApple: ['mac', Validators.required],
|
"bananaCtrl": new Control("", isChecked)
|
||||||
// });
|
});
|
||||||
|
this.fruitsGroup2 = new ControlGroup({
|
||||||
|
"grapeCtrl": new Control("", isChecked),
|
||||||
|
"cherryCtrl": new Control("", isChecked)
|
||||||
|
});
|
||||||
|
|
||||||
|
this.fruitsForm = new ControlGroup({
|
||||||
|
"fruitGroup1": this.fruitsGroup1,
|
||||||
|
"fruitGroup2": this.fruitsGroup2
|
||||||
|
});
|
||||||
|
|
||||||
|
function isChecked(ctrl) {
|
||||||
|
if (ctrl.checked) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'notChecked': true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
doSubmit(event) {
|
||||||
|
console.log('Submitting form', this.fruitsForm.value);
|
||||||
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,33 +3,28 @@
|
|||||||
|
|
||||||
<ion-content>
|
<ion-content>
|
||||||
|
|
||||||
<form (^submit)="doSubmit($event)" [control-group]="form">
|
<form (^submit)="doSubmit($event)" [ng-form-model]="fruitsForm">
|
||||||
<div class="list-header">
|
<div class="list-header">
|
||||||
Radio Group
|
Fruit Group 1
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ion-radio-group control="preferredApple">
|
<ion-radio-group ng-control-group="fruitGroup1">
|
||||||
|
<ion-radio><label>APPLE</label><input #apple type="radio" ng-control="appleCtrl"></ion-radio>
|
||||||
<ion-radio value="mac">
|
<ion-radio><label>BANANA</label><input #banana type="radio" ng-control="bananaCtrl"></ion-radio>
|
||||||
McIntosh
|
|
||||||
</ion-radio>
|
|
||||||
|
|
||||||
<ion-radio value="granny">
|
|
||||||
Granny Smith
|
|
||||||
</ion-radio>
|
|
||||||
|
|
||||||
<ion-radio value="gala">
|
|
||||||
Gala
|
|
||||||
</ion-radio>
|
|
||||||
|
|
||||||
<ion-radio value="fuji">
|
|
||||||
Fuji
|
|
||||||
</ion-radio>
|
|
||||||
|
|
||||||
</ion-radio-group>
|
</ion-radio-group>
|
||||||
|
|
||||||
|
appleCtrl.dirty: {{fruitsForm.controls.fruitGroup1.controls.appleCtrl.dirty}}<br>
|
||||||
|
|
||||||
|
<div class="list-header">
|
||||||
|
Fruit Group 2
|
||||||
|
</div>
|
||||||
|
<ion-radio-group ng-control-group="fruitGroup2">
|
||||||
|
<ion-radio><label>GRAPE</label><input #grape name="test" type="radio" ng-control="grapeCtrl"></ion-radio>
|
||||||
|
<ion-radio><label>CHERRY</label><input #cherry name="test" type="radio" ng-control="cherryCtrl"></ion-radio>
|
||||||
|
</ion-radio-group>
|
||||||
|
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
Current apple: <b>{{form.controls.preferredApple.value}}</b>
|
|
||||||
</ion-content>
|
</ion-content>
|
||||||
</ion-view>
|
</ion-view>
|
||||||
|
@ -57,8 +57,10 @@ export const IonicDirectives = [
|
|||||||
forwardRef(() => SegmentButton),
|
forwardRef(() => SegmentButton),
|
||||||
forwardRef(() => SegmentControlValueAccessor),
|
forwardRef(() => SegmentControlValueAccessor),
|
||||||
forwardRef(() => Checkbox),
|
forwardRef(() => Checkbox),
|
||||||
//Checkbox, Switch
|
forwardRef(() => RadioGroup),
|
||||||
//RadioGroup, RadioButton, SearchBar,
|
forwardRef(() => RadioButton),
|
||||||
|
//Switch
|
||||||
|
//SearchBar,
|
||||||
|
|
||||||
// Input
|
// Input
|
||||||
forwardRef(() => Input),
|
forwardRef(() => Input),
|
||||||
|
Reference in New Issue
Block a user