Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(input): ensure that property bindings work #2431

Merged
merged 3 commits into from
Jan 4, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 60 additions & 7 deletions src/lib/input/input-container.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ describe('MdInputContainer', function () {
MdInputContainerZeroTestController,
MdTextareaWithBindings,
MdInputContainerWithDisabled,
MdInputContainerWithRequired,
MdInputContainerWithType,
MdInputContainerMissingMdInputTestController
],
});
Expand Down Expand Up @@ -236,16 +238,20 @@ describe('MdInputContainer', function () {
let fixture = TestBed.createComponent(MdInputContainerPlaceholderAttrTestComponent);
fixture.detectChanges();

let el = fixture.debugElement.query(By.css('label'));
expect(el).toBeNull();
let inputEl = fixture.debugElement.query(By.css('input')).nativeElement;

expect(fixture.debugElement.query(By.css('label'))).toBeNull();
expect(inputEl.placeholder).toBe('');

fixture.componentInstance.placeholder = 'Other placeholder';
fixture.detectChanges();

el = fixture.debugElement.query(By.css('label'));
expect(el).not.toBeNull();
expect(el.nativeElement.textContent).toMatch('Other placeholder');
expect(el.nativeElement.textContent).not.toMatch(/\*/g);
let labelEl = fixture.debugElement.query(By.css('label'));

expect(inputEl.placeholder).toBe('Other placeholder');
expect(labelEl).not.toBeNull();
expect(labelEl.nativeElement.textContent).toMatch('Other placeholder');
expect(labelEl.nativeElement.textContent).not.toMatch(/\*/g);
}));

it('supports placeholder element', async(() => {
Expand Down Expand Up @@ -274,18 +280,51 @@ describe('MdInputContainer', function () {
expect(el.nativeElement.textContent).toMatch(/hello\s+\*/g);
});

it('supports the disabled attribute', async(() => {
it('supports the disabled attribute as binding', async(() => {
let fixture = TestBed.createComponent(MdInputContainerWithDisabled);
fixture.detectChanges();

let underlineEl = fixture.debugElement.query(By.css('.md-input-underline')).nativeElement;
let inputEl = fixture.debugElement.query(By.css('input')).nativeElement;

expect(underlineEl.classList.contains('md-disabled')).toBe(false, 'should not be disabled');
expect(inputEl.disabled).toBe(false);

fixture.componentInstance.disabled = true;
fixture.detectChanges();

expect(inputEl.disabled).toBe(true);
expect(underlineEl.classList.contains('md-disabled')).toBe(true, 'should be disabled');
}));

it('supports the required attribute as binding', async(() => {
let fixture = TestBed.createComponent(MdInputContainerWithRequired);
fixture.detectChanges();

let inputEl = fixture.debugElement.query(By.css('input')).nativeElement;

expect(inputEl.required).toBe(false);

fixture.componentInstance.required = true;
fixture.detectChanges();

expect(inputEl.required).toBe(true);
}));

it('supports the type attribute as binding', async(() => {
let fixture = TestBed.createComponent(MdInputContainerWithType);
fixture.detectChanges();

let inputEl = fixture.debugElement.query(By.css('input')).nativeElement;

expect(inputEl.type).toBe('text');

fixture.componentInstance.type = 'password';
fixture.detectChanges();

expect(inputEl.type).toBe('password');
}));

it('supports textarea', () => {
let fixture = TestBed.createComponent(MdTextareaWithBindings);
fixture.detectChanges();
Expand All @@ -310,6 +349,20 @@ class MdInputContainerWithDisabled {
disabled: boolean;
}

@Component({
template: `<md-input-container><input mdInput [required]="required"></md-input-container>`
})
class MdInputContainerWithRequired {
required: boolean;
}

@Component({
template: `<md-input-container><input mdInput [type]="type"></md-input-container>`
})
class MdInputContainerWithType {
type: string;
}

@Component({
template: `<md-input-container><input mdInput required placeholder="hello"></md-input-container>`
})
Expand Down
53 changes: 37 additions & 16 deletions src/lib/input/input-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,53 +75,71 @@ export class MdHint {
`,
host: {
'class': 'md-input-element',
// Native input properties that are overwritten by Angular inputs need to be synced with
// the native input element. Otherwise property bindings for those don't work.
'[id]': 'id',
'[placeholder]': 'placeholder',
'[disabled]': 'disabled',
'[required]': 'required',
'(blur)': '_onBlur()',
'(focus)': '_onFocus()',
'(input)': '_onInput()',
}
})
export class MdInputDirective implements AfterContentInit {

/** Variables used as cache for getters and setters. */
private _type = 'text';
private _placeholder: string = '';
private _disabled = false;
private _required = false;
private _id: string;
private _cachedUid: string;

/** The element's value. */
value: any;

/** Whether the element is focused or not. */
focused = false;

/** Whether the element is disabled. */
@Input()
get disabled() { return this._disabled; }
set disabled(value: any) { this._disabled = coerceBooleanProperty(value); }
private _disabled = false;

/** Unique id of the element. */
@Input()
get id() { return this._id; };
set id(value: string) { this._id = value || this._uid; }
private _id: string;
set id(value: string) {this._id = value || this._uid; }

/** Placeholder attribute of the element. */
@Input()
get placeholder() { return this._placeholder; }
set placeholder(value: string) {
if (this._placeholder != value) {
if (this._placeholder !== value) {
this._placeholder = value;
this._placeholderChange.emit(this._placeholder);
}
}
private _placeholder = '';

/** Whether the element is required. */
@Input()
get required() { return this._required; }
set required(value: any) { this._required = coerceBooleanProperty(value); }
private _required = false;

/** Input type of the element. */
@Input()
get type() { return this._type; }
set type(value: string) {
this._type = value || 'text';
this._validateType();
}
private _type = 'text';

/** The element's value. */
value: any;
// When using Angular inputs, developers are no longer able to set the properties on the native
// input element. To ensure that bindings for `type` work, we need to sync the setter
// with the native property. Textarea elements don't support the type property or attribute.
if (!this._isTextarea() && getSupportedInputTypes().has(this._type)) {
this._renderer.setElementProperty(this._elementRef.nativeElement, 'type', this._type);
}
}

/**
* Emits an event when the placeholder changes so that the `md-input-container` can re-validate.
Expand All @@ -130,10 +148,7 @@ export class MdInputDirective implements AfterContentInit {

get empty() { return (this.value == null || this.value === '') && !this._isNeverEmpty(); }

focused = false;

private get _uid() { return this._cachedUid = this._cachedUid || `md-input-${nextUniqueId++}`; }
private _cachedUid: string;

private _neverEmptyInputTypes = [
'date',
Expand Down Expand Up @@ -172,12 +187,18 @@ export class MdInputDirective implements AfterContentInit {

/** Make sure the input is a supported type. */
private _validateType() {
if (MD_INPUT_INVALID_TYPES.indexOf(this._type) != -1) {
if (MD_INPUT_INVALID_TYPES.indexOf(this._type) !== -1) {
throw new MdInputContainerUnsupportedTypeError(this._type);
}
}

private _isNeverEmpty() { return this._neverEmptyInputTypes.indexOf(this._type) != -1; }
private _isNeverEmpty() { return this._neverEmptyInputTypes.indexOf(this._type) !== -1; }

/** Determines if the component host is a textarea. If not recognizable it returns false. */
private _isTextarea() {
let nativeElement = this._elementRef.nativeElement;
return nativeElement ? nativeElement.nodeName.toLowerCase() === 'textarea' : false;
}
}


Expand Down