-
Notifications
You must be signed in to change notification settings - Fork 6.7k
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
Conversation
There was a problem hiding this 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> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2 things;
- since we're not remove the input completely (yet), could you leave 1 here and add the new wrapper?
- could you insert new lines and tabulation? it might affect the baseline.
} | ||
} | ||
|
||
export class MdInputWrapperPlaceholderConflictError extends MdError { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 => { |
There was a problem hiding this comment.
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)?
There was a problem hiding this comment.
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'); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this 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}.`); |
There was a problem hiding this comment.
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' |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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, { |
There was a problem hiding this comment.
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'; } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, and datetime-local
There was a problem hiding this comment.
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', |
There was a problem hiding this comment.
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 => { |
There was a problem hiding this comment.
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; | ||
} | ||
} |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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; });
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
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)
addressed comments, PTAL |
LGTM, I'll let @jelbourn do the rest of the review. |
There was a problem hiding this 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; | ||
}); | ||
})(); |
There was a problem hiding this comment.
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}"'.`); | ||
} | ||
} |
There was a problem hiding this comment.
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>(); |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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(); } |
There was a problem hiding this comment.
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, () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
waitForMutationObserver
obsolete now?
Comments addressed |
|
||
|
||
@Injectable() | ||
export class MdFeatureDetector { |
There was a problem hiding this comment.
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> { |
There was a problem hiding this comment.
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.
Addressed comments & rebased |
LGTM, as discussed we'll hold off on merging until after alpha.11 |
Following 20ee360, Is a version release to npm expected? Current version (beta 11) works the old way. |
How is it supposed to work? I replaced
by
Is this intended? I'm using "@angular/material": "^2.0.0-beta.1" |
mdInput => md-input See the examples here: https://material.angular.io/components/component/input |
The link states
And also does the README at: |
@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. |
I see. |
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
No description provided.