fix(toggle): use value accessor provider

Remove the value property and always recommend ngModel or ngControl.
Closes #5425
This commit is contained in:
Adam Bradley
2016-02-13 00:32:08 -06:00
parent dafa400230
commit 5034c1d729
3 changed files with 109 additions and 113 deletions

View File

@@ -15,6 +15,12 @@ import {
templateUrl: 'main.html'
})
class E2EApp {
fruitsForm: ControlGroup;
grapeDisabled: boolean;
kiwiModel: boolean;
strawberryModel: boolean;
formResults: string;
constructor() {
this.fruitsForm = new ControlGroup({
"appleCtrl": new Control(),
@@ -24,13 +30,13 @@ class E2EApp {
});
this.grapeDisabled = true;
this.grapeChecked = true;
this.myModel = true;
this.kiwiModel = false;
this.strawberryModel = true;
}
toggleGrapeChecked() {
this.grapeChecked = !this.grapeChecked;
this.fruitsForm.controls['grapeCtrl'].updateValue( !this.fruitsForm.controls['grapeCtrl'].value )
}
toggleGrapeDisabled() {

View File

@@ -1,7 +1,6 @@
<ion-toolbar><ion-title>Toggles</ion-title></ion-toolbar>
<ion-content>
<form (submit)="doSubmit($event)" [ngFormModel]="fruitsForm">
@@ -9,33 +8,33 @@
<ion-list>
<ion-item>
<ion-label>Apple, value=apple, init checked</ion-label>
<ion-toggle value="apple" checked="true" ngControl="appleCtrl"></ion-toggle>
<ion-label>Apple, ngControl</ion-label>
<ion-toggle ngControl="appleCtrl"></ion-toggle>
</ion-item>
<ion-item>
<ion-label>Banana, init no checked/value attributes</ion-label>
<ion-label>Banana, ngControl</ion-label>
<ion-toggle ngControl="bananaCtrl"></ion-toggle>
</ion-item>
<ion-item>
<ion-label>Cherry, value=cherry, init disabled</ion-label>
<ion-toggle value="cherry" disabled="true" ngControl="cherryCtrl"></ion-toggle>
<ion-label>Cherry, ngControl, disabled</ion-label>
<ion-toggle disabled="true" ngControl="cherryCtrl"></ion-toggle>
</ion-item>
<ion-item>
<ion-label>Grape, value=grape, init checked, disabled</ion-label>
<ion-toggle value="grape" [checked]="grapeChecked" [disabled]="grapeDisabled" ngControl="grapeCtrl"></ion-toggle>
<ion-label>Grape, ngControl, checked, disabled</ion-label>
<ion-toggle [disabled]="grapeDisabled" ngControl="grapeCtrl"></ion-toggle>
</ion-item>
<ion-item>
<ion-label>Secondary color</ion-label>
<ion-toggle secondary checked="true"></ion-toggle>
<ion-label>Kiwi, NgModel false, Secondary color</ion-label>
<ion-toggle secondary [(ngModel)]="kiwiModel"></ion-toggle>
</ion-item>
<ion-item>
<ion-label>I'm an NgModel</ion-label>
<ion-toggle value="apple" checked="true" [(ngModel)]="myModel"></ion-toggle>
<ion-label>Strawberry, NgModel true</ion-label>
<ion-toggle [(ngModel)]="strawberryModel"></ion-toggle>
</ion-item>
</ion-list>
@@ -57,7 +56,8 @@
<code>cherry.value: {{fruitsForm.controls.cherryCtrl.value}}</code><br>
<code>grape.dirty: {{fruitsForm.controls.grapeCtrl.dirty}}</code><br>
<code>grape.value: {{fruitsForm.controls.grapeCtrl.value}}</code><br>
<code>ngModel.value: {{myModel}}</code><br>
<code>kiwiModel: {{kiwiModel}}</code><br>
<code>strawberryModel: {{strawberryModel}}</code><br>
</p>
<pre aria-hidden="true" padding>{{formResults}}</pre>

View File

@@ -1,13 +1,16 @@
import {Component, ElementRef, Renderer, Input, Optional} from 'angular2/core';
import {NgControl} from 'angular2/common';
import {Component, ElementRef, Renderer, Input, Optional, Provider, Injector, forwardRef} from 'angular2/core';
import {NgControl, ControlValueAccessor, NG_VALUE_ACCESSOR} from 'angular2/common';
import {Form} from '../../util/form';
import {Config} from '../../config/config';
import {isTrueProperty} from '../../util/util';
import {Item} from '../item/item';
import {pointerCoord} from '../../util/dom';
const TOGGLE_VALUE_ACCESSOR = new Provider(
NG_VALUE_ACCESSOR, {useExisting: forwardRef(() => Toggle), multi: true});
/**
* @name Toggle
* @description
@@ -16,11 +19,11 @@ import {pointerCoord} from '../../util/dom';
* Toggles can also have colors assigned to them, by adding any color
* attribute.
*
* See the [Angular 2 Docs](https://angular.io/docs/js/latest/api/forms/) for more info on forms and input.
* @property {any} [value] - the inital value of the toggle
* See the [Angular 2 Docs](https://angular.io/docs/ts/latest/guide/forms.html)
* for more info on forms and input.
* @property {boolean} [checked] - whether the toggle it toggled or not
* @property {boolean} [disabled] - whether the toggle is disabled or not
* @property {string} [id] - a unique ID for a toggle
*
* @usage
* ```html
*
@@ -28,26 +31,27 @@ import {pointerCoord} from '../../util/dom';
*
* <ion-item>
* <ion-label>Pepperoni</ion-label>
* <ion-toggle value="pepperoni" checked="true"></ion-toggle>
* <ion-toggle [(ngModel)]="pepperoni" checked="true"></ion-toggle>
* </ion-item>
*
* <ion-item>
* <ion-label>Sausage</ion-label>
* <ion-toggle value="sausage"></ion-toggle>
* <ion-toggle [(ngModel)]="sausage"></ion-toggle>
* </ion-item>
*
* <ion-item>
* <ion-label>Mushrooms</ion-label>
* <ion-toggle value="mushrooms"></ion-toggle>
* <ion-toggle [(ngModel)]="mushrooms"></ion-toggle>
* </ion-item>
*
* </ion-list>
* ```
*
* @demo /docs/v2/demos/toggle/
* @see {@link /docs/v2/components#toggle Toggle Component Docs}
*/
@Component({
selector: 'ion-toggle,ion-switch',
selector: 'ion-toggle',
template:
'<div class="toggle-icon" [class.toggle-checked]="_checked" [class.toggle-activated]="_activated">' +
'<div class="toggle-inner"></div>' +
@@ -68,47 +72,31 @@ import {pointerCoord} from '../../util/dom';
'</button>',
host: {
'[class.toggle-disabled]': '_disabled'
}
},
providers: [TOGGLE_VALUE_ACCESSOR]
})
export class Toggle {
export class Toggle implements ControlValueAccessor {
private _checked: any = false;
private _disabled: any = false;
private _labelId: string;
private _activated: boolean = false;
private _mode: string;
private _startX;
private _touched: number = 0;
private _startX: number;
private _msPrv: number = 0;
private _fn: Function;
/**
* @private
*/
id: string;
/**
* @private
*/
@Input() value: string = '';
constructor(
private _form: Form,
private _elementRef: ElementRef,
private _renderer: Renderer,
config: Config,
@Optional() ngControl: NgControl,
@Optional() private _item: Item
@Optional() private _item: Item,
private _injector: Injector
) {
// deprecated warning
if (_elementRef.nativeElement.tagName == 'ION-SWITCH') {
console.warn('<ion-switch> has been renamed to <ion-toggle>, please update your HTML');
}
_form.register(this);
this._mode = config.get('mode');
if (ngControl) {
ngControl.valueAccessor = this;
}
this._form.register(this);
if (_item) {
this.id = 'tgl-' + _item.registerInput('toggle');
@@ -117,57 +105,15 @@ export class Toggle {
}
}
/**
* @private
* Toggle the checked state of this toggle.
*/
toggle() {
this.checked = !this.checked;
}
/**
* @private
*/
@Input()
get checked() {
return this._checked;
}
set checked(val) {
if (!this._disabled) {
this._checked = isTrueProperty(val);
this.onChange(this._checked);
this._item && this._item.setCssClass('item-toggle-checked', this._checked);
}
}
/**
* @private
*/
@Input()
get disabled() {
return this._disabled;
}
set disabled(val) {
this._disabled = isTrueProperty(val);
this._item && this._item.setCssClass('item-toggle-disabled', this._disabled);
}
/**
* @private
*/
private pointerDown(ev) {
if (ev.type.indexOf('touch') > -1) {
this._touched = Date.now();
}
if (this.isDisabled(ev)) {
if (this._isPrevented(ev)) {
return;
}
this._startX = pointerCoord(ev).x;
this._activated = true;
}
@@ -176,18 +122,24 @@ export class Toggle {
*/
private pointerMove(ev) {
if (this._startX) {
if (this._isPrevented(ev)) {
return;
}
let currentX = pointerCoord(ev).x;
console.debug('toggle move', ev.type, currentX);
console.debug('toggle, pointerMove', ev.type, currentX);
if (this._checked) {
if (currentX + 15 < this._startX) {
this.toggle();
this.onChange(false);
this._startX = currentX;
this._activated = true;
}
} else if (currentX - 15 > this._startX) {
this.toggle();
this.onChange(true);
this._startX = currentX;
this._activated = (currentX < this._startX + 5);
}
}
}
@@ -198,7 +150,7 @@ export class Toggle {
private pointerUp(ev) {
if (this._startX) {
if (this.isDisabled(ev)) {
if (this._isPrevented(ev)) {
return;
}
@@ -206,10 +158,11 @@ export class Toggle {
if (this.checked) {
if (this._startX + 4 > endX) {
this.toggle();
this.onChange(false);
}
} else if (this._startX - 4 < endX) {
this.toggle();
this.onChange(true);
}
this._activated = false;
@@ -217,37 +170,68 @@ export class Toggle {
}
}
/**
* @private
*/
writeValue(value) {
this.checked = value;
@Input()
get checked(): any {
return this._checked;
}
set checked(val: any) {
this._setChecked(isTrueProperty(val));
this.onChange(this._checked);
}
/**
* @private
*/
onChange(val) {
// TODO: figure the whys and the becauses
writeValue(val: any) {
this._setChecked( isTrueProperty(val) );
}
/**
* @private
*/
onTouched(val) {
// TODO: figure the whys and the becauses
registerOnChange(fn: Function): void {
this._fn = fn;
this.onChange = (isChecked: boolean) => {
console.debug('toggle, onChange', isChecked);
fn(isChecked);
this._setChecked(isChecked);
};
}
/**
* @private
*/
registerOnChange(fn) { this.onChange = fn; }
private _setChecked(isChecked: boolean) {
this._checked = isChecked;
this._item && this._item.setCssClass('item-toggle-checked', isChecked);
}
@Input()
get disabled(): any {
return this._disabled;
}
set disabled(val: any) {
this._disabled = isTrueProperty(val);
this._item && this._item.setCssClass('item-toggle-disabled', this._disabled);
}
/**
* @private
*/
registerOnTouched(fn) { this.onTouched = fn; }
/**
* @private
*/
onChange(_) {}
/**
* @private
*/
onTouched() {}
/**
* @private
*/
@@ -258,9 +242,15 @@ export class Toggle {
/**
* @private
*/
private isDisabled(ev) {
return (this._touched + 999 > Date.now() && (ev.type.indexOf('mouse') > -1))
|| (this._mode == 'ios' && ev.target.tagName == 'ION-TOGGLE');
private _isPrevented(ev) {
if (ev.type.indexOf('touch') > -1) {
this._msPrv = Date.now() + 2000;
} else if (this._msPrv > Date.now() && ev.type.indexOf('mouse') > -1) {
ev.preventDefault();
ev.stopPropagation();
return true;
}
}
}