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

create md-input-container to eventually replace md-input #2052

Merged
merged 35 commits into from
Dec 9, 2016

Conversation

mmalerba
Copy link
Contributor

@mmalerba mmalerba commented Dec 1, 2016

No description provided.

@googlebot googlebot added the cla: yes PR author has agreed to Google's Contributor License Agreement label Dec 1, 2016
Copy link
Contributor

@hansl hansl left a comment

Choose a reason for hiding this comment

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

That's a good start ^_^

@@ -10,7 +10,7 @@
| Text 3 |
<md-radio-button value="option_3">Radio 3</md-radio-button>
| Text 4 |
<md-input>Label</md-input>
<md-input-wrapper><input placeholder="Input"></md-input-wrapper>
Copy link
Contributor

Choose a reason for hiding this comment

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

2 things;

  1. since we're not remove the input completely (yet), could you leave 1 here and add the new wrapper?
  2. could you insert new lines and tabulation? it might affect the baseline.

}
}

export class MdInputWrapperPlaceholderConflictError extends MdError {
Copy link
Contributor

Choose a reason for hiding this comment

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

Seems like those are not imported in input.ts; I think they should be the same class since they're technically the same error.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't see the harm in keeping them separate for now, especially since we're just going to delete the md-input version shortly. It would be weird for MdInput to throw MdInputWrapper* errors

@ContentChild(NgModel) _ngModelChild: NgModel;

/** Whether the `input` or `textarea` is focused. */
_focused = false;
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we need that anymore, since the input is now part of the content tree.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I need this so I can update the focused state of the underline, sadly the DOM structure prevents me from doing it in pure CSS

private _inputElement: HTMLInputElement | HTMLTextAreaElement;

/** A `MutationObserver` to observe the `input` or `textarea`. */
private _inputObserver = new MutationObserver(mutations => {
Copy link
Contributor

Choose a reason for hiding this comment

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

This might make web workers freak out. @jelbourn what's the proper way of handling mutations like those on elements from the content tree? Create a service with an Observable (that would be my guess)?

Copy link
Member

Choose a reason for hiding this comment

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

I'm not worried about it until there's better web-worker support in Angular. Pretty much everything md-input does is DOM-based, so trying to run the code on a worker threat will probably end up being slower due to marshaling.

/** Initialize the `input` or `textarea` element. */
private _initInputEl() {
// Find input or textarea element.
let inputEls = this._elementRef.nativeElement.querySelectorAll('input, textarea');
Copy link
Contributor

Choose a reason for hiding this comment

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

@jelbourn Refresh my memory that we cannot use @ContentChild()? I thought we could but might have been mistaken.

Copy link
Contributor

Choose a reason for hiding this comment

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

If we can't use @ContentChild we'll need to wrap this whole piece in a service.

Copy link
Contributor Author

@mmalerba mmalerba Dec 2, 2016

Choose a reason for hiding this comment

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

We can't use @ContentChild unless we want to require the user to put a directive on the input. (Which we could consider if we don't like all this MutationObserver stuff)


/** The type attribute of the `input` (or "text" if element is a `textarea`). */
private get _inputType(): string { return this._getterSetterOnlyInputType; }
private set _inputType(value: string) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Keeping an internal value for the input type is useless; you can just validate it when the value changes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm keeping a copy so that it's easy to isolate the code that needs access to the input element. I read this value rather than directly reading the type attribute in the _empty getter.

Copy link
Member

@jelbourn jelbourn left a comment

Choose a reason for hiding this comment

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

Initial pass


export class MdInputWrapperInputElementCountError extends MdError {
constructor(count: number) {
super(`md-input-wrapper must contain exactly 1 input or textarea element. Found ${count}.`);
Copy link
Member

Choose a reason for hiding this comment

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

In a follow-up, we'll also want to support using a custom element as an input. E.g., if I make my own component <helpful-input>, we need a way for people to specify that their component acts as the <input> here.

Thinking about it more, though, maybe that means it would be simpler to require a directive on the input element, since we wouldn't be able to rely on the native attributes and events in that case. (I didn't think of this until having my sync with Pantheon yesterday afternoon and they mentioned wanting to do something like this).

selector: 'md-hint',
host: {
'[class.md-right]': 'align == "end"',
'[class.md-hint]': 'true'
Copy link
Member

Choose a reason for hiding this comment

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

You can do

  host: {
    'class': 'md-hint',
    '[class.md-right]': 'align == "end"',
  }

And it won't destroy whatever other classes were set on there (when class is static and not a binding).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

switched to @HostBinding / @HostListener (since I realized that @HostListener actually does have a way to pass variables).

this._inputElement = inputEls[0];

// Install mutation observer and event listeners and subscribe to ngModel changes.
this._inputObserver.observe(this._inputElement, {
Copy link
Member

Choose a reason for hiding this comment

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

Call disconnect in ngOnDestroy?

_inputRequired = false;

/** Whether the `input` or `textarea` is empty. */
get _empty(): boolean { return !this._inputValue && this._inputType !== 'date'; }
Copy link
Member

Choose a reason for hiding this comment

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

Also include datetime, time, month, and week. We should probably break it out into a method like isNeverEmptyInputType or something similar.

Copy link
Member

Choose a reason for hiding this comment

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

Oh, and datetime-local

Copy link
Member

@crisbeto crisbeto Dec 4, 2016

Choose a reason for hiding this comment

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

This won't necessarily be true on browsers that don't support those types, though. E.g. only Chrome supports type="date". That's why I went with the feature test in #1860.

const MD_INPUT_INVALID_INPUT_TYPE = [
'file',
'radio',
'checkbox',
Copy link
Member

Choose a reason for hiding this comment

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

Also "button", "submit", "reset", "color", "range", "image", "hidden"

private _inputElement: HTMLInputElement | HTMLTextAreaElement;

/** A `MutationObserver` to observe the `input` or `textarea`. */
private _inputObserver = new MutationObserver(mutations => {
Copy link
Member

Choose a reason for hiding this comment

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

I'm not worried about it until there's better web-worker support in Angular. Pretty much everything md-input does is DOM-based, so trying to run the code on a worker threat will probably end up being slower due to marshaling.

this._inputRequired = this._inputElement.required;
break;
}
}
Copy link
Member

Choose a reason for hiding this comment

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

I'd move this function to the class, though, i.e.

private _inputObserver = new MutationObserver(mutations => this._updateFromInputMutation(mutations));

attributes: true,
attributeFilter: ['disabled', 'id', 'type', 'placeholder', 'required']
});
for (let eventType in this._inputListeners) {
Copy link
Member

Choose a reason for hiding this comment

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

I think you don't need this extra handling around this._inputListeners; it's less code overall (and less indirection) to just do

inputElement.addEventListener('blur',  () => { this._focused = false; });
inputElement.addEventListener('focus', () => { this._focused = true; });
inputElement.addEventListener('input', () => { this._inputValue = this._inputElement.value; });

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's not really relevant anymore, but I like this pattern when I'm adding and removing a group of listeners at the same time. That way I don't end up with a bunch of boundXListener variables just so I can remove them at the end, I can just iterate over the map removing them

}

/** Initialize the `input` or `textarea` element. */
private _initInputEl() {
Copy link
Member

Choose a reason for hiding this comment

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

_initializeInputElement
(generally prefer the unabbreviated form)

@mmalerba
Copy link
Contributor Author

mmalerba commented Dec 6, 2016

addressed comments, PTAL

@hansl
Copy link
Contributor

hansl commented Dec 6, 2016

LGTM, I'll let @jelbourn do the rest of the review.

Copy link
Member

@jelbourn jelbourn left a comment

Choose a reason for hiding this comment

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

Meta comment: thought about it some more, think we should go with md-input-container; let's chat tomorrow about it.

featureTestInput.setAttribute('type', value);
return featureTestInput.type === value;
});
})();
Copy link
Member

Choose a reason for hiding this comment

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

Move this to a separate getSupportedInputTypes function? Maybe under core/platform

constructor(align: string) {
super(`A hint was already declared for 'align="${align}"'.`);
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Let's move these errors to their own file (which most of the other components do).

@Input()
value: any;

@Output() placeholderChange = new EventEmitter<string>();
Copy link
Member

Choose a reason for hiding this comment

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

Should this be internal (_placeholderChange)? Also needs a description as to what it's for.

focused = false;

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

Choose a reason for hiding this comment

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

Why do you need a separate _uid field instead of setting this.id if it is unset in ngOnInit?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was just trying to be extra defensive. I figured someone could do [id]="myId" and myId would initially have some value but it could theoretically update at some point in the future, so if it updates to null later on this will make sure we still put an id on it.

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

constructor(private _elementRef: ElementRef, @Optional() private _ngModel: NgModel) {
Copy link
Member

Choose a reason for hiding this comment

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

Instead of injection NgModel, you want to inject FormControl (which should have the same APIs that you're using).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If I use FormControl it seems like the valueChanges.subscribe method is not called for an input with [(ngModel)]="value"

I'll wait for @kara's input

// Force setter to be called in case id was not specified.
this.id = this.id;

if (this._ngModel) {
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure you need this; you should be able to inject NG_VALUE_ACCESSOR on the input and return the value from that in a readonly getter (I don't quite follow why this directive needs an @Input for value).

cc @kara in case there's a better way

}

/** Focus the input element. */
focus() { this._elementRef.nativeElement.focus(); }
Copy link
Member

Choose a reason for hiding this comment

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

Might as well go through the renderer for this (#2077)

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

waitForMutationObserver(fixture, () => {
Copy link
Member

Choose a reason for hiding this comment

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

waitForMutationObserver obsolete now?

@mmalerba
Copy link
Contributor Author

mmalerba commented Dec 7, 2016

Comments addressed



@Injectable()
export class MdFeatureDetector {
Copy link
Member

Choose a reason for hiding this comment

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

No Md prefix for this, since it's not really coupled to Material Design.
(I plan on removing all of the Md prefixes from core utilities soon)

@Injectable()
export class MdFeatureDetector {
/** @returns {Set<string>} the input types supported by this browser. */
get supportedInputTypes(): Set<string> {
Copy link
Member

Choose a reason for hiding this comment

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

What's your motivation for making a class rather than individual exported functions similar to coerceBooleanProperty?

I worry that having a MdFeatureDetector class will make people want to start using it as a replacement for modernizr and then complain that it doesn't do what they want.

@mmalerba
Copy link
Contributor Author

mmalerba commented Dec 7, 2016

Addressed comments & rebased

@jelbourn
Copy link
Member

jelbourn commented Dec 7, 2016

LGTM, as discussed we'll hold off on merging until after alpha.11

@mmalerba mmalerba added the action: merge The PR is ready for merge by the caretaker label Dec 9, 2016
@mmalerba mmalerba merged commit ca2046b into angular:master Dec 9, 2016
@mmalerba mmalerba changed the title create md-input-wrapper to eventually replace md-input create md-input-container to eventually replace md-input Dec 14, 2016
@AsherBarak
Copy link

Following 20ee360, Is a version release to npm expected? Current version (beta 11) works the old way.

@jelbourn
Copy link
Member

Soon

@mmalerba mmalerba deleted the input branch December 25, 2016 23:37
@OlliL
Copy link

OlliL commented Jan 7, 2017

How is it supposed to work?

I replaced

      <md-input placeholder="Username"
                [(ngModel)]="username" 
                (keyup.enter)="doLogin()"></md-input>

by

    <md-input-container>
      <input mdInput
             placeholder="Username"
             (keyup.enter)="doLogin()"
             [(ngModel)]="username">
    </md-input-container>

And the style changed from:
md-input

to:
input

Is this intended? I'm using "@angular/material": "^2.0.0-beta.1"

@abdulhaq-e
Copy link

mdInput => md-input

See the examples here: https://material.angular.io/components/component/input

@OlliL
Copy link

OlliL commented Jan 7, 2017

The link states

must be marked with the mdInput directive.

And also does the README at:
https://github.com/angular/material2/blob/master/src/lib/input/README.md

@kara
Copy link
Contributor

kara commented Jan 7, 2017

@OlliL The README is relevant to what's on master only. You'll want to look at the docs site (linked by @abdulhaq-e) for the documentation of the latest release.

@OlliL
Copy link

OlliL commented Jan 7, 2017

I see.
Having multiple <input> elements in one <md-input-container> seems not to render nicely. I'll go for multiple <md-input-container> elements - one for each <input> element for now. This seems to work now fine. Thanks!

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 6, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: merge The PR is ready for merge by the caretaker cla: yes PR author has agreed to Google's Contributor License Agreement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants