Skip to content

Commit

Permalink
fix(input): better handling of attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
manucorporat committed Jun 13, 2017
1 parent 8041eed commit 9f86e10
Show file tree
Hide file tree
Showing 11 changed files with 164 additions and 38 deletions.
44 changes: 11 additions & 33 deletions src/components/input/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import 'rxjs/add/operator/takeUntil';
import { App } from '../app/app';
import { Config } from '../../config/config';
import { Content, ContentDimensions } from '../content/content';
import { hasPointerMoved, pointerCoord } from '../../util/dom';
import { copyInputAttributes, hasPointerMoved, pointerCoord } from '../../util/dom';
import { DomController } from '../../platform/dom-controller';
import { Form, IonicFormInput } from '../../util/form';
import { BaseInput } from '../../util/base-input';
Expand Down Expand Up @@ -93,15 +93,12 @@ import { Platform } from '../../platform/platform';
'(focus)="onFocus($event)" ' +
'(keydown)="onKeydown($event)" ' +
'[type]="_type" ' +
'[attr.name]="name" ' +
'[attr.aria-labelledby]="_labelId" ' +
'[attr.min]="min" ' +
'[attr.max]="max" ' +
'[attr.step]="step" ' +
'[attr.maxlength]="maxlength" ' +
'[attr.autocomplete]="autocomplete" ' +
'[attr.autocorrect]="autocorrect" ' +
'[attr.spellcheck]="spellcheck" ' +
'[attr.autocapitalize]="autocapitalize" ' +
'[placeholder]="placeholder" ' +
'[disabled]="_disabled" ' +
'[readonly]="_readonly">' +
Expand All @@ -111,12 +108,9 @@ import { Platform } from '../../platform/platform';
'(blur)="onBlur($event)" ' +
'(focus)="onFocus($event)" ' +
'(keydown)="onKeydown($event)" ' +
'[attr.name]="name" ' +
'[attr.maxlength]="maxlength" ' +
'[attr.aria-labelledby]="_labelId" ' +
'[attr.autocomplete]="autocomplete" ' +
'[attr.autocorrect]="autocorrect" ' +
'[attr.spellcheck]="spellcheck" ' +
'[attr.autocapitalize]="autocapitalize" ' +
'[placeholder]="placeholder" ' +
'[disabled]="_disabled" ' +
'[readonly]="_readonly"></textarea>' +
Expand All @@ -134,7 +128,8 @@ import { Platform } from '../../platform/platform';
'(mouseup)="_pointerEnd($event)"></div>',

encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
inputs: ['value']
})
export class TextInput extends BaseInput<string> implements IonicFormInput {

Expand Down Expand Up @@ -216,28 +211,11 @@ export class TextInput extends BaseInput<string> implements IonicFormInput {
*/
@Input() autocorrect: string = '';

/**
* @input {string} Specifies whether the element is to have its spelling
* and grammar checked or not.
*/
@Input() spellcheck: string = null;

/**
* @input {string} controls whether and how the text value for textual form control descendants should be automatically capitalized as it is entered/edited by the user.
*/
@Input() autocapitalize: string = null;

/**
* @input {string} Instructional text that shows before the input has a value.
*/
@Input() placeholder: string = '';

/**
* @input {string} The name attribute is used to reference elements in a JavaScript,
* or to reference form data after a form is submitted.
*/
@Input() name: string = null;

/**
* @input {any} The minimum value, which must not be greater than its maximum (max attribute) value.
*/
Expand All @@ -253,11 +231,6 @@ export class TextInput extends BaseInput<string> implements IonicFormInput {
*/
@Input() step: number | string = null;

/**
* @input {any} Specifies the maximum number of characters allowed in the <input> element.
*/
@Input() maxlength: number | string = null;

/**
* @hidden
*/
Expand Down Expand Up @@ -331,10 +304,13 @@ export class TextInput extends BaseInput<string> implements IonicFormInput {
this.clearOnEdit = true;
}
const ionInputEle: HTMLElement = this._elementRef.nativeElement;
const nativeInputEle: HTMLElement = this._native.nativeElement;

// Copy remaining attributes, not handled by ionic/angular
copyInputAttributes(ionInputEle, nativeInputEle);

if (ionInputEle.hasAttribute('autofocus')) {
// the ion-input element has the autofocus attributes
const nativeInputEle: HTMLElement = this._native.nativeElement;
ionInputEle.removeAttribute('autofocus');
switch (this._autoFocusAssist) {
case 'immediate':
Expand All @@ -351,6 +327,8 @@ export class TextInput extends BaseInput<string> implements IonicFormInput {
// traditionally iOS has big issues with autofocus on actual devices
// autoFocus is disabled by default with the iOS mode config
}

// Initialize the input (can start emitting events)
this._initialize();

if (this.focus.observers.length > 0) {
Expand Down
10 changes: 10 additions & 0 deletions src/components/input/test/attributes/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Component } from '@angular/core';

import { RootPage } from '../pages/root-page/root-page';

@Component({
template: '<ion-nav [root]="root"></ion-nav>'
})
export class AppComponent {
root = RootPage;
}
19 changes: 19 additions & 0 deletions src/components/input/test/attributes/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { IonicApp, IonicModule } from '../../../../..';

import { AppComponent } from './app.component';
import { RootPageModule } from '../pages/root-page/root-page.module';

@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
IonicModule.forRoot(AppComponent),
RootPageModule
],
bootstrap: [IonicApp]
})
export class AppModule {}
5 changes: 5 additions & 0 deletions src/components/input/test/attributes/app/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app.module';

platformBrowserDynamic().bootstrapModule(AppModule);
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<ion-header>

<ion-toolbar>
<ion-title>Input attributes</ion-title>
</ion-toolbar>

</ion-header>

<ion-content>
<ion-list>
<ion-item>
<ion-label stacked>Stacked</ion-label>
<ion-input #input1
type="number"
placeholder="Placeholder"
value="1234"
id="mystackinput"
name="holaa"
min="0"
max="10000"
step="2"
autocomplete="on"
autocorrect="on"
autocapitalize="on"
spellcheck="true"
maxlength="4"
disabled
readonly
></ion-input>
</ion-item>

<ion-list>
<ion-item *ngIf="input1Valid" color="secondary">Test passed</ion-item>
<ion-item *ngIf="!input1Valid" color="danger">Test FAILED</ion-item>
</ion-list>

</ion-list>

</ion-content>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { IonicPageModule } from '../../../../../..';

import { RootPage } from './root-page';

@NgModule({
declarations: [
RootPage,
],
imports: [
IonicPageModule.forChild(RootPage)
]
})
export class RootPageModule {}
59 changes: 59 additions & 0 deletions src/components/input/test/attributes/pages/root-page/root-page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Component, ViewChild } from '@angular/core';
import { TextInput } from '../../../../../../';

@Component({
templateUrl: 'root-page.html'
})
export class RootPage {

input1Valid: boolean;
input2Valid: boolean;

@ViewChild('input1') input1: TextInput;

ionViewDidEnter() {
this.input1Valid = this.checkInput1();
}

checkInput1(): boolean {
const nativeEle = <HTMLElement>this.input1._native.nativeElement;

return testAttributes(nativeEle, {
id: null,
type: 'number',
placeholder: 'Placeholder',
name: 'holaa',
min: '0',
max: '10000',
step: '2',
autocomplete: 'on',
autocorrect: 'on',
autocapitalize: 'on',
spellcheck: 'true',
maxLength: '4',
'aria-labelledby': 'lbl-0',
readOnly: true,
disabled: true
});
}
}

function testAttributes(ele: HTMLElement, attributes: any): boolean {
for (let attr in attributes) {
const expected = attributes[attr];
const value = (<any>ele)[attr];

if (expected === null) {
if (ele.hasAttribute(attr) || value !== '') {
console.error(`Element should NOT have "${attr}"`);
return false;
}
} else {
if (expected !== value && expected !== ele.getAttribute(attr)) {
console.error(`Value "${attr}" does not match: ${expected} != ${value}`);
return false;
}
}
}
return true;
}
3 changes: 2 additions & 1 deletion src/components/item/item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ export class Item extends Ion {
this._setName(elementRef);
this._hasReorder = !!reorder;
this.id = form.nextId().toString();
this.labelId = 'lbl-' + this.id;

// auto add "tappable" attribute to ion-item components that have a click listener
if (!(<any>renderer).orgListen) {
Expand Down Expand Up @@ -391,7 +392,7 @@ export class Item extends Ion {
set contentLabel(label: Label) {
if (label) {
this._label = label;
this.labelId = label.id = ('lbl-' + this.id);
label.id = this.labelId;
if (label.type) {
this.setElementClass('item-label-' + label.type, true);
}
Expand Down
3 changes: 2 additions & 1 deletion src/util/base-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,9 @@ export class BaseInput<T> extends Ion implements CommonInput<T> {
this._value = deepCopy(this._defaultValue);

if (_item) {
assert('lbl-' + _item.id === _item.labelId, 'labelId was not calculated correctly');
this.id = name + '-' + _item.registerInput(name);
this._labelId = 'lbl-' + _item.id;
this._labelId = _item.labelId;
this._item.setElementClass('item-' + name, true);
}

Expand Down
6 changes: 3 additions & 3 deletions src/util/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,15 +94,15 @@ export function isTextInput(ele: any) {
export const NON_TEXT_INPUT_REGEX = /^(radio|checkbox|range|file|submit|reset|color|image|button)$/i;


const skipInputAttrsReg = /^(value|checked|disabled|type|class|style|id|autofocus|autocomplete|autocorrect)$/i;
const SKIP_INPUT_ATTR = ['value', 'checked', 'disabled', 'readonly', 'placeholder', 'type', 'class', 'style', 'id', 'autofocus', 'autocomplete', 'autocorrect'];
export function copyInputAttributes(srcElement: HTMLElement, destElement: HTMLElement) {
// copy attributes from one element to another
// however, skip over a few of them as they're already
// handled in the angular world
var attrs = srcElement.attributes;
const attrs = srcElement.attributes;
for (var i = 0; i < attrs.length; i++) {
var attr = attrs[i];
if (!skipInputAttrsReg.test(attr.name)) {
if (SKIP_INPUT_ATTR.indexOf(attr.name) === -1) {
destElement.setAttribute(attr.name, attr.value);
}
}
Expand Down

2 comments on commit 9f86e10

@Manduro
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

@hanzo2001
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that the native input property maxlength is now handled by a utility function called copyInputAttributes. Does this mean that maxlength (as well as other properties) are now static? Should I post this as an issue?

Please sign in to comment.