import {Injectable, NgZone} from 'angular2/angular2'; import {IonicConfig} from '../../config/config'; import {raf} from '../../util/dom'; /** * The Input component is used to focus text input elements. * * The `focusNext()` and `focusPrevious()` methods make it easy to focus input elements across all devices. * * @usage * ```html * * Name * * * ``` */ @Injectable() export class IonicForm { constructor(config: IonicConfig, zone: NgZone) { this._config = config; this._zone = zone; this._inputs = []; this._ids = -1; this._focused = null; zone.runOutsideAngular(() => { this.initHolders(document, this._config.get('keyboardScrollAssist')); if (this._config.get('keyboardInputListener') !== false) { this.initKeyInput(document); } }); } initKeyInput(document) { /* Focus Outline * -------------------------------------------------- * When a keydown event happens, from a tab key, then the * 'key-input' class is added to the body element so focusable * elements have an outline. On a mousedown or touchstart * event then the 'key-input' class is removed. */ let isKeyInputEnabled = false; function keyDown(ev) { if (!isKeyInputEnabled && ev.keyCode == 9) { isKeyInputEnabled = true; raf(enableKeyInput); } } function pointerDown() { isKeyInputEnabled = false; raf(enableKeyInput); } function enableKeyInput() { document.body.classList[isKeyInputEnabled ? 'add' : 'remove']('key-input'); document.removeEventListener('mousedown', pointerDown); document.removeEventListener('touchstart', pointerDown); if (isKeyInputEnabled) { document.addEventListener('mousedown', pointerDown); document.addEventListener('touchstart', pointerDown); } } document.addEventListener('keydown', keyDown); } initHolders(document, scrollAssist) { // raw DOM fun this._ctrl = document.createElement('focus-ctrl'); this._ctrl.setAttribute('aria-hidden', true); if (scrollAssist) { this._prev = document.createElement('input'); this._prev.tabIndex = NO_FOCUS_TAB_INDEX; this._ctrl.appendChild(this._prev); this._next = document.createElement('input'); this._next.tabIndex = NO_FOCUS_TAB_INDEX; this._ctrl.appendChild(this._next); this._temp = document.createElement('input'); this._temp.tabIndex = NO_FOCUS_TAB_INDEX; this._ctrl.appendChild(this._temp); } this._blur = document.createElement('button'); this._blur.tabIndex = NO_FOCUS_TAB_INDEX; this._ctrl.appendChild(this._blur); document.body.appendChild(this._ctrl); function preventDefault(ev) { ev.preventDefault(); ev.stopPropagation(); } if (scrollAssist) { this._prev.addEventListener('keydown', preventDefault); this._next.addEventListener('keydown', preventDefault); this._temp.addEventListener('keydown', preventDefault); this._prev.addEventListener('focus', () => { this.focusPrevious(); }); this._next.addEventListener('focus', () => { this.focusNext(); }); let self = this; let resetTimer; function queueReset() { clearTimeout(resetTimer); resetTimer = setTimeout(function() { self._zone.run(() => { self.resetInputs(); }); }, 100); } document.addEventListener('focusin', queueReset); document.addEventListener('focusout', queueReset); } } focusOut() { console.debug('focusOut') this._blur.tabIndex = NORMAL_FOCUS_TAB_INDEX; this._blur.focus(); this._blur.tabIndex = NO_FOCUS_TAB_INDEX; } setFocusHolder(type) { if (this._temp) { this._temp.tabIndex = TEMP_TAB_INDEX; this._temp.type = type || 'text'; console.debug('setFocusHolder', this._temp.type); this._temp.focus(); } } /** * @param {TODO} input TODO */ register(input) { console.debug('register input', input); input.inputId = ++this._ids; input.tabIndex = NORMAL_FOCUS_TAB_INDEX; this._inputs.push(input); } deregister(input) { console.debug('deregister input', input); let index = this._inputs.indexOf(input); if (index > -1) { this._inputs.splice(index, 1); } if (input === this._focused) { this._focused = null; } } resetInputs() { this._focused = null; for (let i = 0, ii = this._inputs.length; i < ii; i++) { if (!this._focused && this._inputs[i].hasFocus) { this._focused = this._inputs[i]; this._focused.tabIndex = ACTIVE_FOCUS_TAB_INDEX; } else { this._inputs[i].tabIndex = NORMAL_FOCUS_TAB_INDEX; } } if (this._temp) { this._temp.tabIndex = NO_FOCUS_TAB_INDEX; if (this._focused) { // there's a focused input this._prev.tabIndex = PREV_TAB_INDEX; this._next.tabIndex = NEXT_TAB_INDEX; } else { this._prev.tabIndex = this._next.tabIndex = NO_FOCUS_TAB_INDEX; } } } /** * Focuses the previous input element, if it exists. */ focusPrevious() { console.debug('focusPrevious'); this.focusMove(-1); } /** * Focuses the next input element, if it exists. */ focusNext() { console.debug('focusNext'); this.focusMove(1); } /** * @param {Number} inc TODO */ focusMove(inc) { let input = this._focused; if (input) { let index = this._inputs.indexOf(input); if (index > -1 && (index + inc) < this._inputs.length) { let siblingInput = this._inputs[index + inc]; if (siblingInput) { return siblingInput.initFocus(); } } this._focused.initFocus(); } } } const NO_FOCUS_TAB_INDEX = -1; const NORMAL_FOCUS_TAB_INDEX = 0; const PREV_TAB_INDEX = 999; const ACTIVE_FOCUS_TAB_INDEX = 1000; const NEXT_TAB_INDEX = 1001; const TEMP_TAB_INDEX = 2000;