From 94f1e0964e6e2c4883b8bd09aa78178cb0d1802a Mon Sep 17 00:00:00 2001 From: Adam Bradley Date: Tue, 5 Jan 2016 15:03:56 -0600 Subject: [PATCH] refactor(textInput): use ContentChild, code cleanup --- ionic/components/label/label.ts | 49 +- ionic/components/text-input/text-input.ts | 534 +++++++++++----------- 2 files changed, 268 insertions(+), 315 deletions(-) diff --git a/ionic/components/label/label.ts b/ionic/components/label/label.ts index 04f877b8b4..1e85845cf3 100644 --- a/ionic/components/label/label.ts +++ b/ionic/components/label/label.ts @@ -1,8 +1,5 @@ -import {Directive, Optional, ElementRef, Renderer} from 'angular2/core'; +import {Directive, ElementRef, Renderer} from 'angular2/core'; -import {Config} from '../../config/config'; -import {TextInput} from '../text-input/text-input'; -import {pointerCoord, hasPointerMoved} from '../../util/dom'; import {Form} from '../../util/form'; @@ -30,25 +27,16 @@ import {Form} from '../../util/form'; 'id' ], host: { - '[attr.id]': 'id', - '(touchstart)': 'pointerStart($event)', - '(touchend)': 'pointerEnd($event)', - '(mousedown)': 'pointerStart($event)', - '(mouseup)': 'pointerEnd($event)' + '[attr.id]': 'id' } }) export class Label { constructor( - config: Config, - @Optional() container: TextInput, private _form: Form, private _elementRef: ElementRef, private _renderer: Renderer - ) { - this.scrollAssist = config.get('scrollAssist'); - this.container = container; - } + ) {} /** * @private @@ -57,37 +45,6 @@ export class Label { if (!this.id) { this.id = 'lbl-' + this._form.nextId(); } - this.container && this.container.registerLabel(this); - } - - /** - * @private - */ - pointerStart(ev) { - if (this.scrollAssist) { - // remember where the touchstart/mousedown started - this.startCoord = pointerCoord(ev); - } - } - - /** - * @private - */ - pointerEnd(ev) { - if (this.container) { - - // get where the touchend/mouseup ended - let endCoord = pointerCoord(ev); - - // focus this input if the pointer hasn't moved XX pixels - if (!hasPointerMoved(20, this.startCoord, endCoord)) { - ev.preventDefault(); - ev.stopPropagation(); - this.container.initFocus(); - } - - this.startCoord = null; - } } /** diff --git a/ionic/components/text-input/text-input.ts b/ionic/components/text-input/text-input.ts index 35d82fc37e..a930c66402 100644 --- a/ionic/components/text-input/text-input.ts +++ b/ionic/components/text-input/text-input.ts @@ -1,4 +1,4 @@ -import {Component, Directive, Attribute, forwardRef, Host, Optional, ElementRef, Renderer, Input, ContentChild} from 'angular2/core'; +import {Component, Directive, Attribute, forwardRef, Host, Optional, ElementRef, Renderer, Input, Output, EventEmitter, ContentChild, HostListener} from 'angular2/core'; import {NgIf} from 'angular2/common'; import {NavController} from '../nav/nav-controller'; @@ -7,11 +7,131 @@ import {Form} from '../../util/form'; import {Label} from '../label/label'; import {IonicApp} from '../app/app'; import {Content} from '../content/content'; -import * as dom from '../../util/dom'; +import {CSS, hasFocus, pointerCoord, hasPointerMoved} from '../../util/dom'; import {Platform} from '../../platform/platform'; import {Button} from '../button/button'; +/** + * @private + */ +@Directive({ + selector: 'textarea,input[type=text],input[type=password],input[type=number],input[type=search],input[type=email],input[type=url],input[type=tel],input[type=date],input[type=datetime],input[type=datetime-local],input[type=week],input[type=time]', + host: { + 'class': 'text-input' + } +}) +export class TextInputElement { + @Input() value: string; + @Input() ngModel: any; + @Output() valueChange: EventEmitter = new EventEmitter(); + @Output() focusChange: EventEmitter = new EventEmitter(); + + constructor( + @Attribute('type') type: string, + private _elementRef: ElementRef, + private _renderer: Renderer + ) { + this.type = type || 'text'; + } + + ngOnInit() { + if (this.ngModel) { + this.value = this.ngModel; + } else { + this.value = this._elementRef.nativeElement.value; + } + } + + @HostListener('keyup', ['$event']) + _keyup(ev) { + this.valueChange.emit(ev.target.value); + } + + @HostListener('focus') + _focus() { + this.focusChange.emit(true); + } + + @HostListener('blur') + _blur() { + this.focusChange.emit(false); + this.hideFocus(false); + } + + labelledBy(val) { + this._renderer.setElementAttribute(this._elementRef, 'aria-labelledby', val); + } + + setFocus() { + this.element().focus(); + } + + relocate(shouldRelocate, inputRelativeY) { + if (this._relocated !== shouldRelocate) { + + let focusedInputEle = this.element(); + if (shouldRelocate) { + let clonedInputEle = cloneInput(focusedInputEle, 'cloned-input'); + + focusedInputEle.classList.add('hide-focused-input'); + focusedInputEle.style[CSS.transform] = `translate3d(-9999px,${inputRelativeY}px,0)`; + focusedInputEle.parentNode.insertBefore(clonedInputEle, focusedInputEle); + + this.setFocus(); + + } else { + focusedInputEle.classList.remove('hide-focused-input'); + focusedInputEle.style[CSS.transform] = ''; + let clonedInputEle = focusedInputEle.parentNode.querySelector('.cloned-input'); + if (clonedInputEle) { + clonedInputEle.parentNode.removeChild(clonedInputEle); + } + } + + this._relocated = shouldRelocate; + } + } + + hideFocus(shouldHideFocus) { + let focusedInputEle = this.element(); + + if (shouldHideFocus) { + let clonedInputEle = cloneInput(focusedInputEle, 'cloned-hidden'); + + focusedInputEle.classList.add('hide-focused-input'); + focusedInputEle.style[CSS.transform] = 'translate3d(-9999px,0,0)'; + focusedInputEle.parentNode.insertBefore(clonedInputEle, focusedInputEle); + + } else { + focusedInputEle.classList.remove('hide-focused-input'); + focusedInputEle.style[CSS.transform] = ''; + let clonedInputEle = focusedInputEle.parentNode.querySelector('.cloned-hidden'); + if (clonedInputEle) { + clonedInputEle.parentNode.removeChild(clonedInputEle); + } + } + } + + hasFocus() { + return hasFocus(this.element()); + } + + addClass(className) { + this._renderer.setElementClass(this._elementRef, className, true); + } + + hasClass(className) { + this._elementRef.nativeElement.classList.contains(className); + } + + element() { + return this._elementRef.nativeElement; + } + +} + + /** * @name Input * @module ionic @@ -56,49 +176,43 @@ import {Button} from '../button/button'; '(touchend)': 'pointerEnd($event)', '(mouseup)': 'pointerEnd($event)', 'class': 'item', - '[class.ng-untouched]': 'addNgClass("ng-untouched")', - '[class.ng-touched]': 'addNgClass("ng-touched")', - '[class.ng-pristine]': 'addNgClass("ng-pristine")', - '[class.ng-dirty]': 'addNgClass("ng-dirty")', - '[class.ng-valid]': 'addNgClass("ng-valid")', - '[class.ng-invalid]': 'addNgClass("ng-invalid")' + '[class.ng-untouched]': 'hasClass("ng-untouched")', + '[class.ng-touched]': 'hasClass("ng-touched")', + '[class.ng-pristine]': 'hasClass("ng-pristine")', + '[class.ng-dirty]': 'hasClass("ng-dirty")', + '[class.ng-valid]': 'hasClass("ng-valid")', + '[class.ng-invalid]': 'hasClass("ng-invalid")' }, template: '
' + '' + - '' + + '' + '' + '
', directives: [NgIf, forwardRef(() => InputScrollAssist), forwardRef(() => TextInputElement), Button] }) export class TextInput { - @ContentChild(forwardRef(() => TextInputElement)) textInputElement; - /** * @private */ @Input() clearInput: any; - - value: any = ''; + value: string = ''; constructor( - form: Form, - elementRef: ElementRef, config: Config, - renderer: Renderer, - app: IonicApp, - platform: Platform, - @Optional() @Host() scrollView: Content, - @Optional() navCtrl: NavController, + private _form: Form, + private _renderer: Renderer, + private _elementRef: ElementRef, + private _app: IonicApp, + private _platform: Platform, + @Optional() @Host() private _scrollView: Content, + @Optional() private _nav: NavController, @Attribute('floating-label') isFloating: string, @Attribute('stacked-label') isStacked: string, @Attribute('fixed-label') isFixed: string, @Attribute('inset') isInset: string ) { - this.renderer = renderer; - - this.form = form; - form.register(this); + _form.register(this); this.type = 'text'; this.lastTouch = 0; @@ -106,40 +220,44 @@ export class TextInput { // make more gud with pending @Attributes API this.displayType = (isFloating === '' ? 'floating' : (isStacked === '' ? 'stacked' : (isFixed === '' ? 'fixed' : (isInset === '' ? 'inset' : null)))); - this.app = app; - this.elementRef = elementRef; - this.platform = platform; - this.navCtrl = navCtrl; - - this.scrollView = scrollView; - this.scrollAssist = config.get('scrollAssist'); + this._assist = config.get('scrollAssist'); this.keyboardHeight = config.get('keyboardHeight'); } /** * @private - * This function is used to add the Angular css classes associated with inputs in forms */ - addNgClass(className) { - this.input && this.input.elementRef.nativeElement.classList.contains(className); - } + @ContentChild(TextInputElement) + set _setInput(textInputElement) { + if (textInputElement) { + textInputElement.addClass('item-input'); + if (this.displayType) { + textInputElement.addClass(this.displayType + '-input'); + } + this.input = textInputElement; + this.type = textInputElement.type; - /** - * @private - */ - registerInput(textInputElement) { - if (this.displayType) { - textInputElement.addClass(this.displayType + '-input'); + this.hasValue(this.input.value); + textInputElement.valueChange.subscribe(inputValue => { + this.hasValue(inputValue); + }); + + this.focusChange(this.hasFocus()); + textInputElement.focusChange.subscribe(hasFocus => { + this.focusChange(hasFocus); + }); + + } else { + console.error(' or