From b53d70710e8347073603187c54bf4901f479f05c Mon Sep 17 00:00:00 2001 From: Adam Bradley Date: Mon, 22 Feb 2016 15:00:50 -0600 Subject: [PATCH] feat(input): default autocomplete/autocorrect=off, fix autofocus Related: #5480 --- ionic/components/input/input-base.ts | 45 +++++- ionic/components/modal/test/basic/index.ts | 88 ++++++++-- ionic/components/modal/test/basic/main.html | 3 + ionic/config/config.ts | 168 ++++++++++---------- ionic/config/test/config.spec.ts | 8 + ionic/platform/registry.ts | 2 + ionic/util/dom.ts | 6 +- ionic/util/test/util.spec.ts | 8 + ionic/util/util.ts | 2 +- 9 files changed, 228 insertions(+), 102 deletions(-) diff --git a/ionic/components/input/input-base.ts b/ionic/components/input/input-base.ts index f509873966..b9c0d8de08 100644 --- a/ionic/components/input/input-base.ts +++ b/ionic/components/input/input-base.ts @@ -24,6 +24,9 @@ export class InputBase { protected _useAssist: boolean = true; protected _value = ''; protected _isTouch: boolean; + protected _autoFocusAssist: string; + protected _autoComplete: string; + protected _autoCorrect: string; inputControl: NgControl; @@ -45,6 +48,10 @@ export class InputBase { this._useAssist = config.get('scrollAssist'); this._keyboardHeight = config.get('keyboardHeight'); + this._autoFocusAssist = config.get('autoFocusAssist', 'delay'); + this._autoComplete = config.get('autocomplete', 'off'); + this._autoCorrect = config.get('autocorrect', 'off'); + if (ngControl) { ngControl.valueAccessor = this; } @@ -176,8 +183,44 @@ export class InputBase { this.checkHasValue(nativeInput.getValue()); this.disabled = this._disabled; + var ionInputEle: HTMLElement = this._elementRef.nativeElement; + let nativeInputEle: HTMLElement = nativeInput.element(); + // copy ion-input attributes to the native input element - copyInputAttributes(this._elementRef.nativeElement, nativeInput.element()); + copyInputAttributes(ionInputEle, nativeInputEle); + + if (ionInputEle.hasAttribute('autofocus')) { + // the ion-input element has the autofocus attributes + ionInputEle.removeAttribute('autofocus'); + + if (this._autoFocusAssist === 'immediate') { + // config says to immediate focus on the input + // works best on android devices + nativeInputEle.focus(); + + } else if (this._autoFocusAssist === 'delay') { + // config says to chill out a bit and focus on the input after transitions + // works best on desktop + setTimeout(() => { + nativeInputEle.focus(); + }, 650); + } + + // traditionally iOS has big issues with autofocus on actual devices + // autoFocus is disabled by default with the iOS mode config + } + + // by default set autocomplete="off" unless specified by the input + if (ionInputEle.hasAttribute('autocomplete')) { + this._autoComplete = ionInputEle.getAttribute('autocomplete'); + } + nativeInputEle.setAttribute('autocomplete', this._autoComplete); + + // by default set autocomplete="off" unless specified by the input + if (ionInputEle.hasAttribute('autocorrect')) { + this._autoCorrect = ionInputEle.getAttribute('autocorrect'); + } + nativeInputEle.setAttribute('autocorrect', this._autoCorrect); } /** diff --git a/ionic/components/modal/test/basic/index.ts b/ionic/components/modal/test/basic/index.ts index 713e88f314..4da65e1793 100644 --- a/ionic/components/modal/test/basic/index.ts +++ b/ionic/components/modal/test/basic/index.ts @@ -6,9 +6,9 @@ import {Modal, ActionSheet, NavController, NavParams, Transition, TransitionOpti templateUrl: 'main.html' }) class E2EPage { + platforms; - constructor(nav: NavController, config: Config, platform: Platform) { - this.nav = nav; + constructor(private nav: NavController, config: Config, platform: Platform) { console.log('platforms', platform.platforms()); console.log('mode', config.get('mode')); @@ -55,6 +55,14 @@ class E2EPage { }); } + presentModalWithInputs() { + let modal = Modal.create(ModalWithInputs); + modal.onDismiss((data) => { + console.log('Modal with inputs data:', data); + }); + this.nav.present(modal); + } + presentModalCustomAnimation() { let modal = Modal.create(ContactUs); this.nav.present(modal, { @@ -85,12 +93,13 @@ class E2EPage { ` }) class ModalPassData { - constructor(params: NavParams, viewCtrl: ViewController) { + data; + + constructor(params: NavParams, private viewCtrl: ViewController) { this.data = { userId: params.get('userId'), name: 'Jenny' }; - this.viewCtrl = viewCtrl; } submit() { @@ -118,9 +127,7 @@ class ModalPassData { }) class ToolbarModal { - constructor(viewCtrl: ViewController) { - this.viewCtrl = viewCtrl; - } + constructor(private viewCtrl: ViewController) {} dismiss() { this.viewCtrl.emit({ @@ -133,12 +140,66 @@ class ToolbarModal { @Page({ - template: '' + template: ` + + + + + Modal w/ Inputs + + +
+ + + Title (Required) + + + + Note (Required) + + + + Icon + + + +
+ +
+
+
+ ` +}) +class ModalWithInputs { + data; + + constructor(private viewCtrl: ViewController) { + this.data = { + title: 'Title', + note: 'Note', + icon: 'home' + }; + } + + public save(ev) { + this.viewCtrl.dismiss(this.data); + } + + public dismiss() { + this.viewCtrl.dismiss(null); + } +} + + +@Page({ + template: '' }) class ContactUs { + root; + constructor() { console.log('ContactUs constructor'); - this.rootView = ModalFirstPage; + this.root = ModalFirstPage; } onPageLoaded() { console.log('ContactUs onPageLoaded'); @@ -185,9 +246,7 @@ class ContactUs { ` }) class ModalFirstPage { - constructor(nav: NavController) { - this.nav = nav; - } + constructor(private nav: NavController) {} push() { let page = ModalSecondPage; @@ -265,10 +324,9 @@ class ModalFirstPage { }) class ModalSecondPage { constructor( - nav: NavController, + private nav: NavController, params: NavParams ) { - this.nav = nav; console.log('Second page params:', params); } } @@ -278,6 +336,8 @@ class ModalSecondPage { template: '' }) class E2EApp { + root; + constructor() { this.root = E2EPage; } diff --git a/ionic/components/modal/test/basic/main.html b/ionic/components/modal/test/basic/main.html index 81d93a79a9..693650afe4 100644 --- a/ionic/components/modal/test/basic/main.html +++ b/ionic/components/modal/test/basic/main.html @@ -13,6 +13,9 @@

+

+ +

diff --git a/ionic/config/config.ts b/ionic/config/config.ts index 15677f89ff..7d1cb7ab94 100644 --- a/ionic/config/config.ts +++ b/ionic/config/config.ts @@ -117,88 +117,16 @@ export class Config { this._s = config && isObject(config) && !isArray(config) ? config : {}; } - /** - * For setting and getting multiple config values - */ -/** - * @private - * @name settings() - * @description - */ - settings() { - const args = arguments; - - switch (args.length) { - - case 0: - return this._s; - - case 1: - // settings({...}) - this._s = args[0]; - this._c = {}; // clear cache - break; - - case 2: - // settings('ios', {...}) - this._s.platforms = this._s.platforms || {}; - this._s.platforms[args[0]] = args[1]; - this._c = {}; // clear cache - break; - } - - return this; - } - - -/** - * @name set - * @description - * Sets a single config value. - * - * @param {string} [platform] - The platform (either 'ios' or 'android') that the config value should apply to. Leaving this blank will apply the config value to all platforms. - * @param {string} [key] - The key used to look up the value at a later point in time. - * @param {string} [value] - The config value being stored. - */ - set() { - const args = arguments; - const arg0 = args[0]; - const arg1 = args[1]; - - switch (args.length) { - case 2: - // set('key', 'value') = set key/value pair - // arg1 = value - this._s[arg0] = arg1; - delete this._c[arg0]; // clear cache - break; - - case 3: - // setting('ios', 'key', 'value') = set key/value pair for platform - // arg0 = platform - // arg1 = key - // arg2 = value - this._s.platforms = this._s.platforms || {}; - this._s.platforms[arg0] = this._s.platforms[arg0] || {}; - this._s.platforms[arg0][arg1] = args[2]; - delete this._c[arg1]; // clear cache - break; - - } - - return this; - } - - -/** - * @name get - * @description - * Returns a single config value, given a key. - * - * @param {string} [key] - the key for the config value - */ - get(key: string): any { + /** + * @name get + * @description + * Returns a single config value, given a key. + * + * @param {string} [key] - the key for the config value + * @param {any} [fallbackValue] - a fallback value to use when the config value was not found, or is config value is `null`. Fallback value defaults to `null`. + */ + get(key: string, fallbackValue: any = null): any { if (!isDefined(this._c[key])) { if (!isDefined(key)) { @@ -289,13 +217,18 @@ export class Config { // or it was from the users platform configs // or it was from the default platform configs // in that order + let rtnVal; if (isFunction(this._c[key])) { - return this._c[key](this.platform); + rtnVal = this._c[key](this.platform); + + } else { + rtnVal = this._c[key]; } - return this._c[key]; + return (rtnVal !== null ? rtnVal : fallbackValue); } + /** * @name getBoolean * @description @@ -308,6 +241,75 @@ export class Config { return (val || val === 'true') ? true : false; } + + /** + * @name set + * @description + * Sets a single config value. + * + * @param {string} [platform] - The platform (either 'ios' or 'android') that the config value should apply to. Leaving this blank will apply the config value to all platforms. + * @param {string} [key] - The key used to look up the value at a later point in time. + * @param {string} [value] - The config value being stored. + */ + set() { + const args = arguments; + const arg0 = args[0]; + const arg1 = args[1]; + + switch (args.length) { + case 2: + // set('key', 'value') = set key/value pair + // arg1 = value + this._s[arg0] = arg1; + delete this._c[arg0]; // clear cache + break; + + case 3: + // setting('ios', 'key', 'value') = set key/value pair for platform + // arg0 = platform + // arg1 = key + // arg2 = value + this._s.platforms = this._s.platforms || {}; + this._s.platforms[arg0] = this._s.platforms[arg0] || {}; + this._s.platforms[arg0][arg1] = args[2]; + delete this._c[arg1]; // clear cache + break; + + } + + return this; + } + + /** + * @private + * @name settings() + * @description + */ + settings() { + const args = arguments; + + switch (args.length) { + + case 0: + return this._s; + + case 1: + // settings({...}) + this._s = args[0]; + this._c = {}; // clear cache + break; + + case 2: + // settings('ios', {...}) + this._s.platforms = this._s.platforms || {}; + this._s.platforms[args[0]] = args[1]; + this._c = {}; // clear cache + break; + } + + return this; + } + /** * @private */ diff --git a/ionic/config/test/config.spec.ts b/ionic/config/test/config.spec.ts index 1f6acda7ae..b6178273f7 100644 --- a/ionic/config/test/config.spec.ts +++ b/ionic/config/test/config.spec.ts @@ -436,6 +436,14 @@ export function run() { expect(config.get('occupation')).toEqual('Weather Man'); }); + it('should get a fallback value', () => { + let config = new Config({ + name: 'Doc Brown' + }); + expect(config.get('name', 'Marty')).toEqual('Doc Brown'); + expect(config.get('occupation', 'Weather Man')).toEqual('Weather Man'); + }); + it('should get settings object', () => { let config = new Config({ name: 'Doc Brown', diff --git a/ionic/platform/registry.ts b/ionic/platform/registry.ts index 27f3d7c8c8..e776fa6cc1 100644 --- a/ionic/platform/registry.ts +++ b/ionic/platform/registry.ts @@ -70,6 +70,7 @@ Platform.register({ // fallback to always use ripple return 'ripple'; }, + autoFocusAssist: 'immediate', hoverCSS: false, keyboardHeight: 300, mode: 'md', @@ -93,6 +94,7 @@ Platform.register({ 'iphone' ], settings: { + autoFocusAssist: 'delay', clickBlock: true, hoverCSS: false, keyboardHeight: 300, diff --git a/ionic/util/dom.ts b/ionic/util/dom.ts index 5a625284fb..bc080d4693 100644 --- a/ionic/util/dom.ts +++ b/ionic/util/dom.ts @@ -199,13 +199,13 @@ export function hasFocusedTextInput() { return false; } -const skipInputAttrsReg = /^(value|checked|disabled|type|class|style|id)$/i +const skipInputAttrsReg = /^(value|checked|disabled|type|class|style|id|autofocus|autocomplete|autocorrect)$/i export function copyInputAttributes(srcElement, destElement) { // copy attributes from one element to another // however, skip over a few of them as they're already // handled in the angular world - let attrs = srcElement.attributes; - for (let i = 0; i < attrs.length; i++) { + var attrs = srcElement.attributes; + for (var i = 0; i < attrs.length; i++) { var attr = attrs[i]; if (!skipInputAttrsReg.test(attr.name)) { destElement.setAttribute(attr.name, attr.value); diff --git a/ionic/util/test/util.spec.ts b/ionic/util/test/util.spec.ts index 16fc339a16..fe314e63cc 100644 --- a/ionic/util/test/util.spec.ts +++ b/ionic/util/test/util.spec.ts @@ -15,6 +15,10 @@ export function run() { expect(util.isTrueProperty(' true ')).toBe(true); }); + it('should be true from string "on"', () => { + expect(util.isTrueProperty(true)).toBe(true); + }); + it('should be true from empty string ""', () => { expect(util.isTrueProperty('')).toBe(true); expect(util.isTrueProperty(' ')).toBe(true); @@ -29,6 +33,10 @@ export function run() { expect(util.isTrueProperty(false)).toBe(false); }); + it('should be false from string "off"', () => { + expect(util.isTrueProperty(true)).toBe(true); + }); + it('should be false from null', () => { expect(util.isTrueProperty(null)).toBe(false); }); diff --git a/ionic/util/util.ts b/ionic/util/util.ts index d3b5f79424..381608894f 100644 --- a/ionic/util/util.ts +++ b/ionic/util/util.ts @@ -113,7 +113,7 @@ export const isArray = Array.isArray; export const isTrueProperty = function(val: any): boolean { if (typeof val === 'string') { val = val.toLowerCase().trim(); - return (val === 'true' || val === ''); + return (val === 'true' || val === 'on' || val === ''); } return !!val; };