Skip to content
This repository has been archived by the owner on Jan 13, 2025. It is now read-only.

chore(text-field): Pass subelement foundations through MDCTextField super constructor #1684

Merged
merged 14 commits into from
Dec 5, 2017
Merged
Show file tree
Hide file tree
Changes from 4 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
4 changes: 2 additions & 2 deletions packages/mdc-textfield/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -336,8 +336,8 @@ complicated.
| registerBottomLineEventHandler(evtType: string, handler: EventListener) => void | Registers an event listener on the bottom line element for a given event |
| deregisterBottomLineEventHandler(evtType: string, handler: EventListener) => void | Deregisters an event listener on the bottom line element for a given event |
| getNativeInput() => {value: string, disabled: boolean, badInput: boolean, checkValidity: () => boolean}? | Returns an object representing the native text input element, with a similar API shape. The object returned should include the `value`, `disabled` and `badInput` properties, as well as the `checkValidity()` function. We _never_ alter the value within our code, however we _do_ update the disabled property, so if you choose to duck-type the return value for this method in your implementation it's important to keep this in mind. Also note that this method can return null, which the foundation will handle gracefully. |
| getBottomLineFoundation() => MDCTextFieldBottomLineFoundation | Returns the instance of the bottom line element's foundation |
| getHelperTextFoundation() => MDCTextFieldHelperTextFoundation | Returns the instance of the helper text element's foundation |

MDC TextField has multiple optional sub-elements: bottom line and helper text. The foundations of these sub-elements must be passed in as constructor arguments for the MDC TextField foundation. Since the MDCTextField component takes care of creating its foundation, we need to pass sub-element foundations through the MDCTextField component. This is typically done in the component's implementation of `getDefaultFoundation()`.
Copy link
Contributor

Choose a reason for hiding this comment

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

Outside the scope of the PR but is there a standard for how we refer to components? Are they referred to by their back-tick wrapped class name (MDCTextField) or as sentence case (MDC TextField)? Just curious because in some cases it's wrapped in back ticks and others it's not.

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 not sure if there is a formal standard, but I think when it's referenced as a concept then I use sentence case, and when it's referenced as code I use markdown. I changed some references above to be in markdown.


#### The full foundation API

Expand Down
18 changes: 0 additions & 18 deletions packages/mdc-textfield/adapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@
* limitations under the License.
*/

/* eslint-disable no-unused-vars */
import MDCTextFieldBottomLineFoundation from './bottom-line/foundation';
import MDCTextFieldHelperTextFoundation from './helper-text/foundation';

/* eslint no-unused-vars: [2, {"args": "none"}] */

/**
Expand Down Expand Up @@ -142,20 +138,6 @@ class MDCTextFieldAdapter {
* @return {?Element|?NativeInputType}
*/
getNativeInput() {}

/**
* Returns the foundation for the bottom line element. Returns undefined if
* there is no bottom line element.
* @return {?MDCTextFieldBottomLineFoundation}
*/
getBottomLineFoundation() {}

/**
* Returns the foundation for the helper text element. Returns undefined if
* there is no helper text element.
* @return {?MDCTextFieldHelperTextFoundation}
*/
getHelperTextFoundation() {}
}

export {MDCTextFieldAdapter, NativeInputType};
2 changes: 1 addition & 1 deletion packages/mdc-textfield/bottom-line/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class MDCTextFieldBottomLine extends MDCComponent {
}

/**
* @return {MDCTextFieldBottomLineFoundation}
* @return {!MDCTextFieldBottomLineFoundation}
*/
get foundation() {
return this.foundation_;
Expand Down
44 changes: 23 additions & 21 deletions packages/mdc-textfield/foundation.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@
* limitations under the License.
*/

/* eslint-disable no-unused-vars */
Copy link
Contributor

Choose a reason for hiding this comment

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

To ensure checking isn't disabled for the entire file, this line could be removed and a line-specific lint disable comment could be added above MDCTextFieldHelperTextFoundation

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done! Here and in index.js

import MDCFoundation from '@material/base/foundation';
import {MDCTextFieldAdapter, NativeInputType} from './adapter';
import MDCTextFieldBottomLineFoundation from './bottom-line/foundation';
import MDCTextFieldHelperTextFoundation from './helper-text/foundation';
Copy link
Contributor

Choose a reason for hiding this comment

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

See above. Adding this line above line 22 ensures that the no-unused-vars check is only disabled for this one instance and not for the entire file.
// eslint-disable-next-line no-unused-vars

import {cssClasses, strings} from './constants';


Expand Down Expand Up @@ -57,17 +59,23 @@ class MDCTextFieldFoundation extends MDCFoundation {
registerBottomLineEventHandler: () => {},
deregisterBottomLineEventHandler: () => {},
getNativeInput: () => {},
getBottomLineFoundation: () => {},
getHelperTextFoundation: () => {},
});
}

/**
* @param {!MDCTextFieldAdapter=} adapter
* @param {!MDCTextFieldBottomLineFoundation=} bottomLineFoundation
* @param {!MDCTextFieldHelperTextFoundation=} helperTextFoundation
*/
constructor(adapter = /** @type {!MDCTextFieldAdapter} */ ({})) {
constructor(adapter = /** @type {!MDCTextFieldAdapter} */ ({}),
bottomLineFoundation, helperTextFoundation) {
super(Object.assign(MDCTextFieldFoundation.defaultAdapter, adapter));

/** @type {!MDCTextFieldBottomLineFoundation|undefined} */
Copy link
Contributor

Choose a reason for hiding this comment

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

FYI, this is the correct way to annotate an optional property/param. I talked to Brendan (our resident Closure Compiler expert) and crawled through the JS Style Guide and Closure Compiler JSDoc wiki, and they all seem to suggest that your style is the right one.

The rest of our code base uses a nullable type instead of undefined
(e.g., {?MDCTextFieldBottomLineFoundation}), but that's technically incorrect, and our internal Closure Compiler usually complains about it.

We should probably update the rest of our components to use your style of annotations at some point.

this.bottomLine_ = bottomLineFoundation;
/** @type {!MDCTextFieldHelperTextFoundation|undefined} */
this.helperText_ = helperTextFoundation;

/** @private {boolean} */
this.isFocused_ = false;
/** @private {boolean} */
Expand Down Expand Up @@ -150,15 +158,13 @@ class MDCTextFieldFoundation extends MDCFoundation {
activateFocus() {
const {FOCUSED, LABEL_FLOAT_ABOVE, LABEL_SHAKE} = MDCTextFieldFoundation.cssClasses;
this.adapter_.addClass(FOCUSED);
const bottomLine = this.adapter_.getBottomLineFoundation();
if (bottomLine) {
bottomLine.activate();
if (this.bottomLine_) {
this.bottomLine_.activate();
}
this.adapter_.addClassToLabel(LABEL_FLOAT_ABOVE);
this.adapter_.removeClassFromLabel(LABEL_SHAKE);
const helperText = this.adapter_.getHelperTextFoundation();
if (helperText) {
helperText.showToScreenReader();
if (this.helperText_) {
this.helperText_.showToScreenReader();
}
this.isFocused_ = true;
}
Expand All @@ -169,9 +175,8 @@ class MDCTextFieldFoundation extends MDCFoundation {
* @param {!Event} evt
*/
setBottomLineTransformOrigin(evt) {
const bottomLine = this.adapter_.getBottomLineFoundation();
if (bottomLine) {
bottomLine.setTransformOrigin(evt);
if (this.bottomLine_) {
this.bottomLine_.setTransformOrigin(evt);
}
}

Expand All @@ -190,12 +195,11 @@ class MDCTextFieldFoundation extends MDCFoundation {
* for animations to finish.
*/
handleBottomLineAnimationEnd() {
const bottomLine = this.adapter_.getBottomLineFoundation();
// We need to wait for the bottom line to be entirely transparent
// before removing the class. If we do not, we see the line start to
// scale down before disappearing
if (!this.isFocused_ && bottomLine) {
bottomLine.deactivate();
if (!this.isFocused_ && this.bottomLine_) {
this.bottomLine_.deactivate();
}
}

Expand Down Expand Up @@ -233,9 +237,8 @@ class MDCTextFieldFoundation extends MDCFoundation {
this.adapter_.addClassToLabel(LABEL_SHAKE);
this.adapter_.addClass(INVALID);
}
const helperText = this.adapter_.getHelperTextFoundation();
if (helperText) {
helperText.setValidity(isValid);
if (this.helperText_) {
this.helperText_.setValidity(isValid);
}
}

Expand Down Expand Up @@ -275,9 +278,8 @@ class MDCTextFieldFoundation extends MDCFoundation {
* @param {string} content Sets the content of the helper text.
*/
setHelperTextContent(content) {
const helperText = this.adapter_.getHelperTextFoundation();
if (helperText) {
helperText.setContent(content);
if (this.helperText_) {
this.helperText_.setContent(content);
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/mdc-textfield/helper-text/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class MDCTextFieldHelperText extends MDCComponent {
}

/**
* @return {MDCTextFieldHelperTextFoundation}
* @return {!MDCTextFieldHelperTextFoundation}
*/
get foundation() {
return this.foundation_;
Expand Down
106 changes: 61 additions & 45 deletions packages/mdc-textfield/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@
import MDCComponent from '@material/base/component';
import {MDCRipple} from '@material/ripple';

/* eslint-disable no-unused-vars */
Copy link
Contributor

Choose a reason for hiding this comment

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

Same as above. Could be removed to ensure unused var check is only removed for specific lines

import {cssClasses, strings} from './constants';
import {MDCTextFieldAdapter} from './adapter';
import MDCTextFieldFoundation from './foundation';
import {MDCTextFieldBottomLine} from './bottom-line';
import {MDCTextFieldHelperText} from './helper-text';
import {MDCTextFieldBottomLine, MDCTextFieldBottomLineFoundation} from './bottom-line';
import {MDCTextFieldHelperText, MDCTextFieldHelperTextFoundation} from './helper-text';
Copy link
Contributor

Choose a reason for hiding this comment

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

Same as above. Adding // eslint-disable-next-line no-unused-vars ensures only specific lines are allowed to ignore rule


/**
* @extends {MDCComponent<!MDCTextFieldFoundation>}
Expand Down Expand Up @@ -142,50 +143,41 @@ class MDCTextField extends MDCComponent {
* @return {!MDCTextFieldFoundation}
*/
getDefaultFoundation() {
return new MDCTextFieldFoundation(/** @type {!MDCTextFieldAdapter} */ (Object.assign({
addClass: (className) => this.root_.classList.add(className),
removeClass: (className) => this.root_.classList.remove(className),
addClassToLabel: (className) => {
const label = this.label_;
if (label) {
label.classList.add(className);
}
},
removeClassFromLabel: (className) => {
const label = this.label_;
if (label) {
label.classList.remove(className);
}
},
eventTargetHasClass: (target, className) => target.classList.contains(className),
registerTextFieldInteractionHandler: (evtType, handler) => this.root_.addEventListener(evtType, handler),
deregisterTextFieldInteractionHandler: (evtType, handler) => this.root_.removeEventListener(evtType, handler),
notifyIconAction: () => this.emit(MDCTextFieldFoundation.strings.ICON_EVENT, {}),
registerBottomLineEventHandler: (evtType, handler) => {
if (this.bottomLine_) {
this.bottomLine_.listen(evtType, handler);
}
},
deregisterBottomLineEventHandler: (evtType, handler) => {
if (this.bottomLine_) {
this.bottomLine_.unlisten(evtType, handler);
}
},
getBottomLineFoundation: () => {
if (this.bottomLine_) {
return this.bottomLine_.foundation;
}
return undefined;
return new MDCTextFieldFoundation(
/** @type {!MDCTextFieldAdapter} */ (Object.assign({
addClass: (className) => this.root_.classList.add(className),
removeClass: (className) => this.root_.classList.remove(className),
addClassToLabel: (className) => {
const label = this.label_;
if (label) {
label.classList.add(className);
}
},
removeClassFromLabel: (className) => {
const label = this.label_;
if (label) {
label.classList.remove(className);
}
},
eventTargetHasClass: (target, className) => target.classList.contains(className),
registerTextFieldInteractionHandler: (evtType, handler) => this.root_.addEventListener(evtType, handler),
deregisterTextFieldInteractionHandler: (evtType, handler) => this.root_.removeEventListener(evtType, handler),
notifyIconAction: () => this.emit(MDCTextFieldFoundation.strings.ICON_EVENT, {}),
registerBottomLineEventHandler: (evtType, handler) => {
if (this.bottomLine_) {
this.bottomLine_.listen(evtType, handler);
}
},
deregisterBottomLineEventHandler: (evtType, handler) => {
if (this.bottomLine_) {
this.bottomLine_.unlisten(evtType, handler);
}
},
},
getHelperTextFoundation: () => {
if (this.helperText_) {
return this.helperText_.foundation;
}
return undefined;
},
},
this.getInputAdapterMethods_(),
this.getIconAdapterMethods_())));
this.getInputAdapterMethods_(),
this.getIconAdapterMethods_())),
this.getBottomLineFoundation_(),
this.getHelperTextFoundation_());
}

/**
Expand Down Expand Up @@ -217,6 +209,30 @@ class MDCTextField extends MDCComponent {
getNativeInput: () => this.input_,
};
}

/**
* Returns the foundation for the bottom line element. Returns undefined if
* there is no bottom line element.
* @return {!MDCTextFieldBottomLineFoundation|undefined}
*/
getBottomLineFoundation_() {
if (this.bottomLine_) {
return this.bottomLine_.foundation;
}
return undefined;
}

/**
* Returns the foundation for the helper text element. Returns undefined if
* there is no helper text element.
* @return {!MDCTextFieldHelperTextFoundation|undefined}
*/
getHelperTextFoundation_() {
if (this.helperText_) {
return this.helperText_.foundation;
}
return undefined;
}
}

export {MDCTextField, MDCTextFieldFoundation};
Loading