From 9a4d81b3292996539b6c65c2a644eddced29f158 Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Wed, 22 Mar 2017 00:10:52 +0100 Subject: [PATCH] refactor(all): consistent inputs fixes #8578 --- .../alert/test/basic/app/app.module.ts | 4 +- src/components/checkbox/checkbox.ts | 165 +++-------- src/components/checkbox/test/checkbox.spec.ts | 24 ++ src/components/datetime/datetime.ts | 235 +++++----------- src/components/input/input.ts | 105 ++----- .../events/pages/root-page/root-page.html | 5 + src/components/option/option.ts | 34 ++- src/components/range/range.ts | 205 +++++--------- src/components/range/test/range.spec.ts | 15 +- src/components/searchbar/searchbar.ts | 128 +++------ src/components/segment/segment.ts | 103 ++----- src/components/select/select.ts | 168 ++---------- src/components/toggle/test/toggle.spec.ts | 28 ++ src/components/toggle/toggle.ts | 169 ++++-------- src/util/base-input.ts | 259 ++++++++++++++++++ src/util/input-tester.ts | 200 ++++++++++++++ src/util/mock-providers.ts | 38 ++- src/util/test/base-input.spec.ts | 55 ++++ src/util/util.ts | 21 +- 19 files changed, 961 insertions(+), 1000 deletions(-) create mode 100644 src/components/checkbox/test/checkbox.spec.ts create mode 100644 src/components/toggle/test/toggle.spec.ts create mode 100644 src/util/base-input.ts create mode 100644 src/util/input-tester.ts create mode 100644 src/util/test/base-input.spec.ts diff --git a/src/components/alert/test/basic/app/app.module.ts b/src/components/alert/test/basic/app/app.module.ts index c2bb850b8f..8b037e7d58 100644 --- a/src/components/alert/test/basic/app/app.module.ts +++ b/src/components/alert/test/basic/app/app.module.ts @@ -11,7 +11,9 @@ import { PageOneModule } from '../pages/page-one/page-one.module'; ], imports: [ BrowserModule, - IonicModule.forRoot(AppComponent, {}), + IonicModule.forRoot(AppComponent, { + mode: 'ios' + }), PageOneModule ], bootstrap: [IonicApp] diff --git a/src/components/checkbox/checkbox.ts b/src/components/checkbox/checkbox.ts index 77afd2b753..b70f3ac96c 100644 --- a/src/components/checkbox/checkbox.ts +++ b/src/components/checkbox/checkbox.ts @@ -1,10 +1,10 @@ -import { AfterContentInit, ChangeDetectorRef, Component, ElementRef, EventEmitter, forwardRef, HostListener, Input, OnDestroy, Optional, Output, Renderer, ViewEncapsulation } from '@angular/core'; -import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { AfterViewInit, ChangeDetectorRef, Component, ElementRef, forwardRef, HostListener, Input, OnDestroy, Optional, Renderer, ViewEncapsulation } from '@angular/core'; +import { NG_VALUE_ACCESSOR } from '@angular/forms'; import { Config } from '../../config/config'; -import { Form, IonicTapInput } from '../../util/form'; -import { Ion } from '../ion'; import { isTrueProperty } from '../../util/util'; +import { Form, IonicTapInput } from '../../util/form'; +import { BaseInput } from '../../util/base-input'; import { Item } from '../item/item'; export const CHECKBOX_VALUE_ACCESSOR: any = { @@ -54,14 +54,14 @@ export const CHECKBOX_VALUE_ACCESSOR: any = { @Component({ selector: 'ion-checkbox', template: - '
' + + '
' + '
' + '
' + '', + '
{{placeholder}}
' + + '
{{_text}}
' + + '', host: { '[class.datetime-disabled]': '_disabled' }, providers: [DATETIME_VALUE_ACCESSOR], encapsulation: ViewEncapsulation.None, }) -export class DateTime extends Ion implements AfterContentInit, ControlValueAccessor, OnDestroy { - _disabled: any = false; - _labelId: string; +export class DateTime extends BaseInput implements AfterContentInit, ControlValueAccessor, OnDestroy { + _text: string = ''; - _fn: Function; - _isOpen: boolean = false; _min: DateTimeData; _max: DateTimeData; - _value: DateTimeData = {}; + _timeValue: DateTimeData = {}; _locale: LocaleData = {}; _picker: Picker; - /** - * @hidden - */ - id: string; - /** * @input {string} The minimum datetime allowed. Value must be a date string * following the @@ -421,39 +413,56 @@ export class DateTime extends Ion implements AfterContentInit, ControlValueAcces */ @Input() placeholder: string = ''; - /** - * @output {any} Emitted when the datetime selection has changed. - */ - @Output() ionChange: EventEmitter = new EventEmitter(); - /** * @output {any} Emitted when the datetime selection was cancelled. */ @Output() ionCancel: EventEmitter = new EventEmitter(); constructor( - private _form: Form, + form: Form, config: Config, elementRef: ElementRef, renderer: Renderer, - @Optional() private _item: Item, + @Optional() item: Item, @Optional() private _pickerCtrl: PickerController ) { - super(config, elementRef, renderer, 'datetime'); + super(config, elementRef, renderer, 'datetime', form, item, null); + } - _form.register(this); + /** + * @hidden + */ + ngAfterContentInit() { + // first see if locale names were provided in the inputs + // then check to see if they're in the config + // if neither were provided then it will use default English names + ['monthNames', 'monthShortNames', 'dayNames', 'dayShortNames'].forEach(type => { + (this)._locale[type] = convertToArrayOfStrings(isPresent((this)[type]) ? (this)[type] : this._config.get(type), type); + }); - if (_item) { - this.id = 'dt-' + _item.registerInput('datetime'); - this._labelId = 'lbl-' + _item.id; - this._item.setElementClass('item-datetime', true); - } + // update how the datetime value is displayed as formatted text + this.updateText(); + } + + /** + * @hidden + */ + _inputUpdated() { + updateDate(this._timeValue, this.value); + this.updateText(); + } + + /** + * @hidden + */ + _inputShouldChange(): boolean { + return true; } @HostListener('click', ['$event']) _click(ev: UIEvent) { + // do not continue if the click event came from a form submit if (ev.detail === 0) { - // do not continue if the click event came from a form submit return; } ev.preventDefault(); @@ -463,17 +472,14 @@ export class DateTime extends Ion implements AfterContentInit, ControlValueAcces @HostListener('keyup.space') _keyup() { - if (!this._isOpen) { - this.open(); - } + this.open(); } /** * @hidden */ open() { - assert(!this._isOpen, 'datetime is already open'); - if (this._disabled) { + if (this._isFocus || this._disabled) { return; } console.debug('datetime, open picker'); @@ -481,6 +487,7 @@ export class DateTime extends Ion implements AfterContentInit, ControlValueAcces // the user may have assigned some options specifically for the alert const pickerOptions = deepCopy(this.pickerOptions); + // Configure picker under the hood const picker = this._picker = this._pickerCtrl.create(pickerOptions); picker.addButton({ text: this.cancelText, @@ -489,27 +496,24 @@ export class DateTime extends Ion implements AfterContentInit, ControlValueAcces }); picker.addButton({ text: this.doneText, - handler: (data: any) => { - console.debug('datetime, done', data); - this.onChange(data); - this.ionChange.emit(data); - } + handler: (data: any) => this.value = data, }); - this.generate(); - this.validate(); - picker.ionChange.subscribe(() => { this.validate(); picker.refresh(); }); - this._isOpen = true; - picker.onDidDismiss(() => { - this._isOpen = false; - }); + // Update picker status before presenting + this.generate(); + this.validate(); + // Present picker + this._setFocus(); picker.present(pickerOptions); + picker.onDidDismiss(() => { + this._setBlur(); + }); } /** @@ -566,7 +570,7 @@ export class DateTime extends Ion implements AfterContentInit, ControlValueAcces // cool, we've loaded up the columns with options // preselect the option for this column - const optValue = getValueFromFormat(this._value, format); + const optValue = getValueFromFormat(this.getValue(), format); const selectedIndex = column.options.findIndex(opt => opt.value === optValue); if (selectedIndex >= 0) { // set the select index for this column's options @@ -729,33 +733,17 @@ export class DateTime extends Ion implements AfterContentInit, ControlValueAcces /** * @hidden */ - setValue(newData: any) { - updateDate(this._value, newData); + updateText() { + // create the text of the formatted data + const template = this.displayFormat || this.pickerFormat || DEFAULT_FORMAT; + this._text = renderDateTime(template, this.getValue(), this._locale); } /** * @hidden */ getValue(): DateTimeData { - return this._value; - } - - /** - * @hidden - */ - checkHasValue(inputValue: any) { - if (this._item) { - this._item.setElementClass('input-has-value', !!(inputValue && inputValue !== '')); - } - } - - /** - * @hidden - */ - updateText() { - // create the text of the formatted data - const template = this.displayFormat || this.pickerFormat || DEFAULT_FORMAT; - this._text = renderDateTime(template, this._value, this._locale); + return this._timeValue; } /** @@ -812,97 +800,6 @@ export class DateTime extends Ion implements AfterContentInit, ControlValueAcces } } - /** - * @input {boolean} If true, the user cannot interact with this element. - */ - @Input() - get disabled(): boolean { - return this._disabled; - } - - set disabled(val: boolean) { - this._disabled = isTrueProperty(val); - this._item && this._item.setElementClass('item-datetime-disabled', this._disabled); - } - - /** - * @hidden - */ - writeValue(val: any) { - console.debug('datetime, writeValue', val); - this.setValue(val); - this.updateText(); - this.checkHasValue(val); - } - - /** - * @hidden - */ - ngAfterContentInit() { - // first see if locale names were provided in the inputs - // then check to see if they're in the config - // if neither were provided then it will use default English names - ['monthNames', 'monthShortNames', 'dayNames', 'dayShortNames'].forEach(type => { - (this)._locale[type] = convertToArrayOfStrings(isPresent((this)[type]) ? (this)[type] : this._config.get(type), type); - }); - - // update how the datetime value is displayed as formatted text - this.updateText(); - } - - /** - * @hidden - */ - registerOnChange(fn: Function): void { - this._fn = fn; - this.onChange = (val: any) => { - console.debug('datetime, onChange', val); - this.setValue(val); - this.updateText(); - this.checkHasValue(val); - - // convert DateTimeData value to iso datetime format - fn(convertDataToISO(this._value)); - - this.onTouched(); - }; - } - - /** - * @hidden - */ - registerOnTouched(fn: any) { this.onTouched = fn; } - - /** - * @hidden - */ - onChange(val: any) { - // onChange used when there is not an formControlName - console.debug('datetime, onChange w/out formControlName', val); - this.setValue(val); - this.updateText(); - this.checkHasValue(val); - this.onTouched(); - } - - /** - * @hidden - */ - onTouched() { } - - /** - * @hidden - */ - setDisabledState(isDisabled: boolean) { - this.disabled = isDisabled; - } - - /** - * @hidden - */ - ngOnDestroy() { - this._form.deregister(this); - } } /** diff --git a/src/components/input/input.ts b/src/components/input/input.ts index 5ec3b82be5..f5253ab3c8 100644 --- a/src/components/input/input.ts +++ b/src/components/input/input.ts @@ -7,7 +7,7 @@ import { Content, ContentDimensions, ScrollEvent } from '../content/content'; import { copyInputAttributes, PointerCoordinates, hasPointerMoved, pointerCoord } from '../../util/dom'; import { DomController } from '../../platform/dom-controller'; import { Form, IonicFormInput } from '../../util/form'; -import { Ion } from '../ion'; +import { BaseInput } from '../../util/base-input'; import { isString, isTrueProperty } from '../../util/util'; import { Item } from '../item/item'; import { NativeInput } from './native-input'; @@ -91,7 +91,8 @@ import { Platform } from '../../platform/platform'; '
', encapsulation: ViewEncapsulation.None, }) -export class TextInput extends Ion implements IonicFormInput { +export class TextInput extends BaseInput implements IonicFormInput { + _autoComplete: string; _autoCorrect: string; _autoFocusAssist: string; @@ -121,17 +122,17 @@ export class TextInput extends Ion implements IonicFormInput { constructor( config: Config, private _plt: Platform, - private _form: Form, + form: Form, private _app: App, elementRef: ElementRef, renderer: Renderer, @Optional() private _content: Content, - @Optional() private _item: Item, + @Optional() item: Item, @Optional() nav: NavController, @Optional() public ngControl: NgControl, private _dom: DomController ) { - super(config, elementRef, renderer, 'input'); + super(config, elementRef, renderer, 'input', form, item, ngControl); this._nav = nav; @@ -147,12 +148,10 @@ export class TextInput extends Ion implements IonicFormInput { } if (ngControl) { - ngControl.valueAccessor = this; + // ngControl.valueAccessor = this; this.inputControl = ngControl; } - _form.register(this); - // only listen to content scroll events if there is content if (_content) { this._scrollStart = _content.ionScrollStart.subscribe((ev: ScrollEvent) => { @@ -162,8 +161,6 @@ export class TextInput extends Ion implements IonicFormInput { this.scrollHideFocus(ev, false); }); } - - this.mode = config.get('mode'); } /** @@ -182,18 +179,6 @@ export class TextInput extends Ion implements IonicFormInput { this._clearInput = (this._type !== TEXTAREA && isTrueProperty(val)); } - /** - * @input {string} The text value of the input. - */ - @Input() - get value() { - return this._value; - } - set value(val: any) { - this._value = val; - this.checkHasValue(val); - } - /** * @input {string} The type of control to display. The default type is text. Possible values are: `"text"`, `"password"`, `"email"`, `"number"`, `"search"`, `"tel"`, or `"url"`. */ @@ -215,31 +200,21 @@ export class TextInput extends Ion implements IonicFormInput { } } - /** - * @input {boolean} If true, the user cannot interact with this element. - */ - @Input() - get disabled(): boolean { - return this._disabled; - } - set disabled(val: boolean) { - this.setDisabled(this._disabled = isTrueProperty(val)); - } - /** * @hidden */ setDisabled(val: boolean) { - this._renderer.setElementAttribute(this._elementRef.nativeElement, 'disabled', val ? '' : null); - this._item && this._item.setElementClass('item-input-disabled', val); - this._native && this._native.isDisabled(val); + this.setDisabledState(val); } /** * @hidden */ setDisabledState(isDisabled: boolean) { - this.disabled = isDisabled; + this._disabled = isDisabled; + this._renderer.setElementAttribute(this._elementRef.nativeElement, 'disabled', isDisabled ? '' : null); + this._item && this._item.setElementClass('item-input-disabled', isDisabled); + this._native && this._native.isDisabled(isDisabled); } /** @@ -376,8 +351,7 @@ export class TextInput extends Ion implements IonicFormInput { } nativeInput.valueChange.subscribe((inputValue: any) => { - this.onChange(inputValue); - this.checkHasValue(inputValue); + this.value = inputValue; }); nativeInput.keydown.subscribe((inputValue: any) => { @@ -387,13 +361,12 @@ export class TextInput extends Ion implements IonicFormInput { this.focusChange(this.hasFocus()); nativeInput.focusChange.subscribe((textInputHasFocus: any) => { this.focusChange(textInputHasFocus); - this.checkHasValue(nativeInput.getValue()); + // this.checkHasValue(nativeInput.getValue()); if (!textInputHasFocus) { this.onTouched(textInputHasFocus); } }); - - this.checkHasValue(nativeInput.getValue()); + this.value = nativeInput.getValue(); var ionInputEle: HTMLElement = this._elementRef.nativeElement; var nativeInputEle: HTMLElement = nativeInput.element(); @@ -556,21 +529,6 @@ export class TextInput extends Ion implements IonicFormInput { this.focus.emit(ev); } - /** - * @hidden - */ - writeValue(val: any) { - this._value = val; - this.checkHasValue(val); - } - - /** - * @hidden - */ - onChange(val: any) { - this.checkHasValue(val); - } - /** * @hidden */ @@ -601,16 +559,6 @@ export class TextInput extends Ion implements IonicFormInput { return (inputValue !== null && inputValue !== undefined && inputValue !== ''); } - /** - * @hidden - */ - checkHasValue(inputValue: any) { - if (this._item) { - var hasValue = (inputValue !== null && inputValue !== undefined && inputValue !== ''); - this._item.setElementClass('input-has-value', hasValue); - } - } - /** * @hidden */ @@ -722,7 +670,7 @@ export class TextInput extends Ion implements IonicFormInput { * @hidden */ ngOnDestroy() { - this._form.deregister(this); + super.ngOnDestroy(); // only stop listening to content scroll events if there is content if (this._content) { @@ -736,9 +684,7 @@ export class TextInput extends Ion implements IonicFormInput { */ clearTextInput() { console.debug('Should clear input'); - this._value = ''; - this.onChange(this._value); - this.writeValue(this._value); + this.value = ''; } /** @@ -760,23 +706,6 @@ export class TextInput extends Ion implements IonicFormInput { this._didBlurAfterEdit = false; } - /** - * @hidden - * Angular2 Forms API method called by the view (formControlName) to register the - * onChange event handler that updates the model (Control). - * @param {Function} fn the onChange event handler. - */ - registerOnChange(fn: any) { this.onChange = fn; } - - /** - * @hidden - * Angular2 Forms API method called by the view (formControlName) to register - * the onTouched event handler that marks model (Control) as touched. - * @param {Function} fn onTouched event handler. - */ - registerOnTouched(fn: any) { this.onTouched = fn; } - - /** * @hidden */ diff --git a/src/components/input/test/events/pages/root-page/root-page.html b/src/components/input/test/events/pages/root-page/root-page.html index 853a915624..c7b4cb3415 100644 --- a/src/components/input/test/events/pages/root-page/root-page.html +++ b/src/components/input/test/events/pages/root-page/root-page.html @@ -45,6 +45,11 @@ + + Date of Birth + + + diff --git a/src/components/option/option.ts b/src/components/option/option.ts index 8e589b358c..6fb090c69b 100644 --- a/src/components/option/option.ts +++ b/src/components/option/option.ts @@ -13,26 +13,30 @@ import { isPresent, isTrueProperty } from '../../util/util'; selector: 'ion-option' }) export class Option { - _selected: any = false; - _disabled: any = false; + + _selected: boolean = false; + _disabled: boolean = false; _value: any; /** - * @output {any} Event to evaluate when option is selected. + * @input {boolean} If true, the user cannot interact with this element. */ - @Output() ionSelect: EventEmitter = new EventEmitter(); - - constructor(private _elementRef: ElementRef) {} + @Input() + get disabled(): boolean { + return this._disabled; + } + set disabled(val: boolean) { + this._disabled = isTrueProperty(val); + } /** * @input {boolean} If true, the element is selected. */ @Input() - get selected() { + get selected(): boolean { return this._selected; } - - set selected(val) { + set selected(val: boolean) { this._selected = isTrueProperty(val); } @@ -46,22 +50,16 @@ export class Option { } return this.text; } - set value(val: any) { this._value = val; } /** - * @input {boolean} If true, the user cannot interact with this element. + * @output {any} Event to evaluate when option is selected. */ - @Input() - get disabled(): boolean { - return this._disabled; - } + @Output() ionSelect: EventEmitter = new EventEmitter(); - set disabled(val: boolean) { - this._disabled = isTrueProperty(val); - } + constructor(private _elementRef: ElementRef) {} /** * @hidden diff --git a/src/components/range/range.ts b/src/components/range/range.ts index b2571097a0..0bc7c3f668 100644 --- a/src/components/range/range.ts +++ b/src/components/range/range.ts @@ -1,12 +1,12 @@ -import { AfterViewInit, ChangeDetectorRef, Component, ElementRef, EventEmitter, forwardRef, Input, OnDestroy, Optional, Output, Renderer, ViewChild, ViewEncapsulation } from '@angular/core'; +import { AfterViewInit, ChangeDetectorRef, Component, ElementRef, forwardRef, Input, OnDestroy, Optional, Renderer, ViewChild, ViewEncapsulation } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; -import { clamp, isPresent, isTrueProperty } from '../../util/util'; +import { clamp, isTrueProperty } from '../../util/util'; import { Config } from '../../config/config'; import { DomController } from '../../platform/dom-controller'; import { Form } from '../../util/form'; import { Haptic } from '../../tap-click/haptic'; -import { Ion } from '../ion'; +import { BaseInput } from '../../util/base-input'; import { Item } from '../item/item'; import { Platform } from '../../platform/platform'; import { PointerCoordinates, pointerCoord } from '../../util/dom'; @@ -112,13 +112,11 @@ export const RANGE_VALUE_ACCESSOR: any = { providers: [RANGE_VALUE_ACCESSOR], encapsulation: ViewEncapsulation.None, }) -export class Range extends Ion implements AfterViewInit, ControlValueAccessor, OnDestroy { +export class Range extends BaseInput implements AfterViewInit, ControlValueAccessor, OnDestroy { + _dual: boolean; _pin: boolean; - _disabled: boolean = false; _pressed: boolean; - _lblId: string; - _fn: Function; _activeB: boolean; _rect: ClientRect; @@ -146,16 +144,6 @@ export class Range extends Ion implements AfterViewInit, ControlValueAccessor, O @ViewChild('slider') public _slider: ElementRef; - /** - * @hidden - */ - value: any; - - /** - * @hidden - */ - id: string; - /** * @input {number} Minimum integer value of the range. Defaults to `0`. */ @@ -245,19 +233,6 @@ export class Range extends Ion implements AfterViewInit, ControlValueAccessor, O this._dual = isTrueProperty(val); } - /** - * @input {boolean} If true, the user cannot interact with this element. - */ - @Input() - get disabled(): boolean { - return this._disabled; - } - set disabled(val: boolean) { - this._disabled = val = isTrueProperty(val); - const item = this._item; - item && item.setElementClass('item-range-disabled', val); - } - /** * Returns the ratio of the knob's is current location, which is a number * between `0` and `1`. If two knobs are used, this property represents @@ -282,25 +257,10 @@ export class Range extends Ion implements AfterViewInit, ControlValueAccessor, O return null; } - /** - * @output {Range} Emitted when the range selector drag starts. - */ - @Output() ionFocus: EventEmitter = new EventEmitter(); - - /** - * @output {Range} Emitted when the range value changes. - */ - @Output() ionChange: EventEmitter = new EventEmitter(); - - /** - * @output {Range} Emitted when the range selector drag ends. - */ - @Output() ionBlur: EventEmitter = new EventEmitter(); - constructor( - private _form: Form, + form: Form, private _haptic: Haptic, - @Optional() private _item: Item, + @Optional() item: Item, config: Config, private _plt: Platform, elementRef: ElementRef, @@ -308,21 +268,17 @@ export class Range extends Ion implements AfterViewInit, ControlValueAccessor, O private _dom: DomController, private _cd: ChangeDetectorRef ) { - super(config, elementRef, renderer, 'range'); + super(config, elementRef, renderer, 'range', form, item, null); this._events = new UIEventManager(_plt); - _form.register(this); - - if (_item) { - this.id = 'rng-' + _item.registerInput('range'); - this._lblId = 'lbl-' + _item.id; - _item.setElementClass('item-range', true); - } + this._value = 0; } /** * @hidden */ ngAfterViewInit() { + this._initialize(); + // add touchstart/mousedown listeners this._events.pointerEvents({ element: this._slider.nativeElement, @@ -346,7 +302,7 @@ export class Range extends Ion implements AfterViewInit, ControlValueAccessor, O } // trigger ionFocus event - this.ionFocus.emit(this); + this._setFocus(); // prevent default so scrolling does not happen ev.preventDefault(); @@ -375,38 +331,40 @@ export class Range extends Ion implements AfterViewInit, ControlValueAccessor, O /** @internal */ _pointerMove(ev: UIEvent) { - if (!this._disabled) { - // prevent default so scrolling does not happen - ev.preventDefault(); - ev.stopPropagation(); + if (this._disabled) { + return; + } + // prevent default so scrolling does not happen + ev.preventDefault(); + ev.stopPropagation(); - // update the active knob's position - const hasChanged = this._update(pointerCoord(ev), this._rect, true); + // update the active knob's position + const hasChanged = this._update(pointerCoord(ev), this._rect, true); - if (hasChanged && this._snaps) { - // trigger a haptic selection changed event - // if this is a snap range - this._haptic.gestureSelectionChanged(); - } + if (hasChanged && this._snaps) { + // trigger a haptic selection changed event + // if this is a snap range + this._haptic.gestureSelectionChanged(); } } /** @internal */ _pointerUp(ev: UIEvent) { - if (!this._disabled) { - // prevent default so scrolling does not happen - ev.preventDefault(); - ev.stopPropagation(); - - // update the active knob's position - this._update(pointerCoord(ev), this._rect, false); - - // trigger a haptic end - this._haptic.gestureSelectionEnd(); - - // trigger ionBlur event - this.ionBlur.emit(this); + if (this._disabled) { + return; } + // prevent default so scrolling does not happen + ev.preventDefault(); + ev.stopPropagation(); + + // update the active knob's position + this._update(pointerCoord(ev), this._rect, false); + + // trigger a haptic end + this._haptic.gestureSelectionEnd(); + + // trigger ionBlur event + this._setBlur(); } /** @internal */ @@ -447,27 +405,24 @@ export class Range extends Ion implements AfterViewInit, ControlValueAccessor, O } // value has been updated + let value; if (this._dual) { // dual knobs have an lower and upper value - if (!this.value) { - // ensure we're always updating the same object - this.value = {}; - } - this.value.lower = Math.min(this._valA, this._valB); - this.value.upper = Math.max(this._valA, this._valB); + value = { + lower: Math.min(this._valA, this._valB), + upper: Math.max(this._valA, this._valB) + }; console.debug(`range, updateKnob: ${ratio}, lower: ${this.value.lower}, upper: ${this.value.upper}`); } else { // single knob only has one value - this.value = this._valA; + value = this._valA; console.debug(`range, updateKnob: ${ratio}, value: ${this.value}`); } - this._debouncer.debounce(() => { - this.onChange(this.value); - this.ionChange.emit(this); - }); + // Update input value + this.value = value; return true; } @@ -566,70 +521,40 @@ export class Range extends Ion implements AfterViewInit, ControlValueAccessor, O return clamp(0, value, 1); } - /** - * @hidden - */ - writeValue(val: any) { - if (isPresent(val)) { - this.value = val; - - if (this._dual) { - this._valA = val.lower; - this._valB = val.upper; - this._ratioA = this._valueToRatio(val.lower); - this._ratioB = this._valueToRatio(val.upper); - - } else { - this._valA = val; - this._ratioA = this._valueToRatio(val); - } - - this._updateBar(); + _inputNormalize(val: any): any { + if (this._dual) { + return val; + } else { + val = parseFloat(val); + return isNaN(val) ? undefined : val; } } /** * @hidden */ - registerOnChange(fn: Function): void { - this._fn = fn; - this.onChange = (val: any) => { - fn(val); - this.onTouched(); - }; - } + _inputUpdated() { + const val = this.value; + if (this._dual) { + this._valA = val.lower; + this._valB = val.upper; + this._ratioA = this._valueToRatio(val.lower); + this._ratioB = this._valueToRatio(val.upper); - /** - * @hidden - */ - registerOnTouched(fn: any) { this.onTouched = fn; } + } else { + this._valA = val; + this._ratioA = this._valueToRatio(val); + } - /** - * @hidden - */ - onChange(val: any) { - // used when this input does not have an ngModel or formControlName - this.onTouched(); + this._updateBar(); this._cd.detectChanges(); } - /** - * @hidden - */ - onTouched() { } - - /** - * @hidden - */ - setDisabledState(isDisabled: boolean) { - this.disabled = isDisabled; - } - /** * @hidden */ ngOnDestroy() { - this._form.deregister(this); + super.ngOnDestroy(); this._events.destroy(); } } diff --git a/src/components/range/test/range.spec.ts b/src/components/range/test/range.spec.ts index 28887dfe9d..d60e999a14 100644 --- a/src/components/range/test/range.spec.ts +++ b/src/components/range/test/range.spec.ts @@ -1,10 +1,21 @@ import { Range } from '../range'; -import { mockChangeDetectorRef, mockConfig, mockDomController, mockElementRef, mockHaptic, mockPlatform, mockRenderer } from '../../../util/mock-providers'; +import { mockChangeDetectorRef, mockConfig, mockDomController, mockItem, mockElementRef, mockHaptic, mockPlatform, mockRenderer } from '../../../util/mock-providers'; import { Form } from '../../../util/form'; +import { commonInputTest, NUMBER_CORPUS } from '../../../util/input-tester'; describe('Range', () => { + it('should pass common test', () => { + const range = createRange(); + range._slider = mockElementRef(); + commonInputTest(range, { + defaultValue: 0, + corpus: NUMBER_CORPUS + }); + + }); + describe('valueToRatio', () => { it('step=1', () => { let range = createRange(); @@ -68,5 +79,5 @@ describe('Range', () => { function createRange(): Range { let form = new Form(); - return new Range(form, mockHaptic(), null, mockConfig(), mockPlatform(), mockElementRef(), mockRenderer(), mockDomController(), mockChangeDetectorRef()); + return new Range(form, mockHaptic(), mockItem(), mockConfig(), mockPlatform(), mockElementRef(), mockRenderer(), mockDomController(), mockChangeDetectorRef()); } diff --git a/src/components/searchbar/searchbar.ts b/src/components/searchbar/searchbar.ts index 58fd05f459..34b8ee9f82 100644 --- a/src/components/searchbar/searchbar.ts +++ b/src/components/searchbar/searchbar.ts @@ -1,8 +1,8 @@ import { Component, ElementRef, EventEmitter, HostBinding, Input, Optional, Output, Renderer, ViewChild, ViewEncapsulation } from '@angular/core'; -import { NgControl } from '@angular/forms'; +import { NgControl } from '@angular/forms'; import { Config } from '../../config/config'; -import { Ion } from '../ion'; +import { BaseInput } from '../../util/base-input'; import { isPresent, isTrueProperty } from '../../util/util'; import { Platform } from '../../platform/platform'; import { TimeoutDebouncer } from '../../util/debouncer'; @@ -53,9 +53,8 @@ import { TimeoutDebouncer } from '../../util/debouncer'; }, encapsulation: ViewEncapsulation.None }) -export class Searchbar extends Ion { +export class Searchbar extends BaseInput { - _value: string|number = ''; _shouldBlur: boolean = true; _shouldAlignLeft: boolean = true; _isCancelVisible: boolean = false; @@ -144,16 +143,6 @@ export class Searchbar extends Ion { */ @Output() ionInput: EventEmitter = new EventEmitter(); - /** - * @output {event} Emitted when the Searchbar input has blurred. - */ - @Output() ionBlur: EventEmitter = new EventEmitter(); - - /** - * @output {event} Emitted when the Searchbar input has focused. - */ - @Output() ionFocus: EventEmitter = new EventEmitter(); - /** * @output {event} Emitted when the cancel button is clicked. */ @@ -176,12 +165,7 @@ export class Searchbar extends Ion { renderer: Renderer, @Optional() ngControl: NgControl ) { - super(config, elementRef, renderer, 'searchbar'); - - // If the user passed a ngControl we need to set the valueAccessor - if (ngControl) { - ngControl.valueAccessor = this; - } + super(config, elementRef, renderer, 'searchbar', null, null, ngControl); } @ViewChild('searchbarInput') _searchbarInput: ElementRef; @@ -191,21 +175,12 @@ export class Searchbar extends Ion { @ViewChild('cancelButton', {read: ElementRef}) _cancelButton: ElementRef; /** - * @input {string} Set the input value. + * @hidden + * After View Checked position the elements */ - @Input() - get value() { - return this._value; - } - - set value(val) { - this._value = val; - if (this._searchbarInput) { - let ele = this._searchbarInput.nativeElement; - if (ele) { - ele.value = val; - } - } + ngAfterViewInit() { + this._initialize(); + this.positionElements(); } /** @@ -213,7 +188,7 @@ export class Searchbar extends Ion { * On Initialization check for attributes */ ngOnInit() { - let showCancelButton = this.showCancelButton; + const showCancelButton = this.showCancelButton; if (typeof showCancelButton === 'string') { this.showCancelButton = (showCancelButton === '' || showCancelButton === 'true'); } @@ -221,9 +196,14 @@ export class Searchbar extends Ion { /** * @hidden - * After View Checked position the elements */ - ngAfterContentInit() { + _inputUpdated() { + if (this._searchbarInput) { + var ele = this._searchbarInput.nativeElement; + if (ele) { + ele.value = this.value; + } + } this.positionElements(); } @@ -233,9 +213,9 @@ export class Searchbar extends Ion { * based on the input value and if it is focused. (ios only) */ positionElements() { - let isAnimated = this._animated; - let prevAlignLeft = this._shouldAlignLeft; - let shouldAlignLeft = (!isAnimated || (this._value && this._value.toString().trim() !== '') || this._sbHasFocus === true); + const isAnimated = this._animated; + const prevAlignLeft = this._shouldAlignLeft; + const shouldAlignLeft = (!isAnimated || (this._value && this._value.toString().trim() !== '') || this._sbHasFocus === true); this._shouldAlignLeft = shouldAlignLeft; if (this._mode !== 'ios') { @@ -254,8 +234,8 @@ export class Searchbar extends Ion { if (!this._searchbarInput || !this._searchbarIcon) { return; } - let inputEle = this._searchbarInput.nativeElement; - let iconEle = this._searchbarIcon.nativeElement; + const inputEle = this._searchbarInput.nativeElement; + const iconEle = this._searchbarIcon.nativeElement; if (this._shouldAlignLeft) { inputEle.removeAttribute('style'); @@ -290,15 +270,15 @@ export class Searchbar extends Ion { if (!this._cancelButton || !this._cancelButton.nativeElement) { return; } - let showShowCancel = this._sbHasFocus; + const showShowCancel = this._sbHasFocus; if (showShowCancel !== this._isCancelVisible) { - let cancelStyleEle = this._cancelButton.nativeElement; - let cancelStyle = cancelStyleEle.style; + var cancelStyleEle = this._cancelButton.nativeElement; + var cancelStyle = cancelStyleEle.style; this._isCancelVisible = showShowCancel; if (showShowCancel) { cancelStyle.marginRight = '0'; } else { - let offset = cancelStyleEle.offsetWidth; + var offset = cancelStyleEle.offsetWidth; if (offset > 0) { cancelStyle.marginRight = -offset + 'px'; } @@ -312,11 +292,7 @@ export class Searchbar extends Ion { * Update the Searchbar input value when the input changes */ inputChanged(ev: any) { - this._value = ev.target.value; - this._debouncer.debounce(() => { - this.onChange(this._value); - this.ionInput.emit(ev); - }); + this.value = ev.target.value; } /** @@ -324,11 +300,8 @@ export class Searchbar extends Ion { * Sets the Searchbar to focused and active on input focus. */ inputFocused(ev: UIEvent) { - this.ionFocus.emit(ev); - - this._sbHasFocus = true; + this._setFocus(); this._isActive = true; - this.positionElements(); } /** @@ -344,10 +317,7 @@ export class Searchbar extends Ion { this._shouldBlur = true; return; } - this.ionBlur.emit(ev); - - this._sbHasFocus = false; - this.positionElements(); + this._setBlur(); } /** @@ -363,8 +333,6 @@ export class Searchbar extends Ion { let value = this._value; if (isPresent(value) && value !== '') { this.value = ''; // DOM WRITE - this.onChange(this._value); - this.ionInput.emit(ev); } }, 16 * 4); this._shouldBlur = false; @@ -384,42 +352,8 @@ export class Searchbar extends Ion { this._isActive = false; } - /** - * @hidden - * Write a new value to the element. - */ - writeValue(val: any) { - this.value = val; - this.positionElements(); - } - - /** - * @hidden - */ - onChange = (_: any) => {}; - - /** - * @hidden - */ - onTouched = () => {}; - - /** - * @hidden - * Set the function to be called when the control receives a change event. - */ - registerOnChange(fn: (_: any) => {}): void { - this.onChange = fn; - } - - /** - * @hidden - * Set the function to be called when the control receives a touch event. - */ - registerOnTouched(fn: () => {}): void { - this.onTouched = fn; - } - - setFocus() { + _setFocus() { this._renderer.invokeElementMethod(this._searchbarInput.nativeElement, 'focus'); + super._setFocus(); } } diff --git a/src/components/segment/segment.ts b/src/components/segment/segment.ts index 1a90cb6713..b4ad72154c 100644 --- a/src/components/segment/segment.ts +++ b/src/components/segment/segment.ts @@ -1,10 +1,8 @@ -import { ContentChildren, Directive, ElementRef, EventEmitter, Input, Output, Optional, QueryList, Renderer } from '@angular/core'; +import { ContentChildren, Directive, ElementRef, Optional, QueryList, Renderer } from '@angular/core'; import { NgControl } from '@angular/forms'; import { Config } from '../../config/config'; -import { Ion } from '../ion'; -import { isPresent, isTrueProperty } from '../../util/util'; - +import { BaseInput } from '../../util/base-input'; import { SegmentButton } from './segment-button'; /** @@ -66,19 +64,7 @@ import { SegmentButton } from './segment-button'; @Directive({ selector: 'ion-segment' }) -export class Segment extends Ion { - _disabled: boolean = false; - - /** - * @hidden - */ - value: string; - - /** - * @output {Any} Emitted when a segment button has been changed. - */ - @Output() ionChange: EventEmitter = new EventEmitter(); - +export class Segment extends BaseInput { /** * @hidden @@ -91,85 +77,32 @@ export class Segment extends Ion { renderer: Renderer, @Optional() ngControl: NgControl ) { - super(config, elementRef, renderer, 'segment'); - - if (ngControl) { - ngControl.valueAccessor = this; - } - } - - /** - * @input {boolean} If true, the user cannot interact with any of the buttons in the segment. - */ - @Input() - get disabled(): boolean { - return this._disabled; - } - - set disabled(val: boolean) { - this._disabled = isTrueProperty(val); - - if (this._buttons) { - this._buttons.forEach(button => { - button._setElementClass('segment-button-disabled', this._disabled); - }); - } - } - - /** - * @hidden - * Write a new value to the element. - */ - writeValue(value: any) { - this.value = isPresent(value) ? value : ''; - if (this._buttons) { - let buttons = this._buttons.toArray(); - for (let button of buttons) { - button.isActive = (button.value === this.value); - } - } + super(config, elementRef, renderer, 'segment', null, null, ngControl); } /** * @hidden */ ngAfterViewInit() { - this._buttons.forEach(button => { - button.ionSelect.subscribe((selectedButton: any) => { - this.writeValue(selectedButton.value); - this.onChange(selectedButton.value); - this.ionChange.emit(selectedButton); - }); - - if (isPresent(this.value)) { - button.isActive = (button.value === this.value); - } - - if (isTrueProperty(this._disabled)) { - button._setElementClass('segment-button-disabled', this._disabled); - } - }); + this._initialize(); + this._buttons.forEach(button => { + button.ionSelect.subscribe((selectedButton: any) => this.value = selectedButton.value); + }); } /** * @hidden + * Write a new value to the element. */ - onChange = (_: any) => {}; - /** - * @hidden - */ - onTouched = (_: any) => {}; + _inputUpdated() { + if (this._buttons) { + var buttons = this._buttons.toArray(); + var value = this.value; + for (var button of buttons) { + button.isActive = (button.value === value); + } + } + } - /** - * @hidden - * Set the function to be called when the control receives a change event. - */ - registerOnChange(fn: any) { this.onChange = fn; } - - /** - * @hidden - * Set the function to be called when the control receives a touch event. - */ - registerOnTouched(fn: any) { this.onTouched = fn; } } diff --git a/src/components/select/select.ts b/src/components/select/select.ts index 0899ab6b74..72197010cd 100644 --- a/src/components/select/select.ts +++ b/src/components/select/select.ts @@ -1,13 +1,13 @@ -import { AfterContentInit, Component, ContentChildren, ElementRef, EventEmitter, forwardRef, Input, HostListener, OnDestroy, Optional, Output, Renderer, QueryList, ViewEncapsulation } from '@angular/core'; -import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { AfterViewInit, Component, ContentChildren, ElementRef, EventEmitter, forwardRef, Input, HostListener, OnDestroy, Optional, Output, Renderer, QueryList, ViewEncapsulation } from '@angular/core'; +import { NG_VALUE_ACCESSOR } from '@angular/forms'; import { ActionSheet } from '../action-sheet/action-sheet'; import { Alert } from '../alert/alert'; import { App } from '../app/app'; import { Config } from '../../config/config'; import { Form } from '../../util/form'; -import { Ion } from '../ion'; -import { isBlank, isCheckedProperty, isTrueProperty, deepCopy } from '../../util/util'; +import { BaseInput } from '../../util/base-input'; +import { isCheckedProperty, isTrueProperty, isBlank, deepCopy } from '../../util/util'; import { Item } from '../item/item'; import { NavController } from '../../navigation/nav-controller'; import { Option } from '../option/option'; @@ -141,21 +141,13 @@ export const SELECT_VALUE_ACCESSOR: any = { providers: [SELECT_VALUE_ACCESSOR], encapsulation: ViewEncapsulation.None, }) -export class Select extends Ion implements AfterContentInit, ControlValueAccessor, OnDestroy { - _disabled: any = false; - _labelId: string; +export class Select extends BaseInput implements AfterViewInit, OnDestroy { + _multi: boolean = false; _options: QueryList