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

feat(chip-list): Implement FormFieldControl and ControlValueAccessor in chip list #6686

Merged
merged 7 commits into from
Sep 18, 2017

Conversation

tinayuangao
Copy link
Contributor

@tinayuangao tinayuangao commented Aug 29, 2017

Closes #5999 #6077

  • Made md-chip's API similar to md-option
  • Made md-chip-list's API similar to md-select

Breaking changes in MdChip:

  • Removed @Output() select = EventEmitter<MdChipEvent> and @Output() deselect = EventEmitter<MdChipEvent>
  • Added @Output() onSelectionChange = EventEmitter<MdChipSelectionChange>

@googlebot googlebot added the cla: yes PR author has agreed to Google's Contributor License Agreement label Aug 29, 2017
Copy link
Contributor

@mmalerba mmalerba left a comment

Choose a reason for hiding this comment

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

Still need to take a closer look at the code, but something I noticed in the demo: it's hard to focus the actual input sometimes. Clicking on the md-form-field / md-chip-list should focus the input rather than one of the chips

}
})
export class MdChipInput {
export class MdChipInput extends MdInput {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we check for something like: <input mdInput mdChipInputFor="chips"> and throw an error? People might just be used to adding the mdInput and I'm not sure what would happen in this case if they did that... I assume it would break things.

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 think it's okay to use something like <input mdInput mdChipInputFor="chips">.

Copy link
Contributor

@mmalerba mmalerba Aug 31, 2017

Choose a reason for hiding this comment

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

They won't conflict with each other since this extends MdInput? It seems like at the very least you would be doing twice the amount of work for no reason, since both directives would react to every change in value and try to do things.

Actually looking at the code a little more closely, I see that MdChipList implements MdFormFieldControl and handles communication with the MdFormField so why does this need to extend MdInput at all? If there's enough shared stuff between this and MdInput you could maybe factor it into a common base class, but I don't think it really makes sense for this to directly extend MdInput

Copy link
Contributor

Choose a reason for hiding this comment

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

@tinayuangao still curious about why this needs to extend MdInput, seems unnecessary to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@mmalerba Changed MdChipInput to not extends MdInput. Added focus(), focused and empty to work with chip list. Please take another look. Thanks!

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.

Are there breaking changes that should be marked in this PR?

@@ -19,12 +38,13 @@ export interface MdChipInputEvent {
@Directive({
Copy link
Member

Choose a reason for hiding this comment

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

Could you add a class description for this directive that talks about how it operates as a control for md-form-field?

protected _ariaDescribedby: string;

/** Id of the chip list */
protected _id: string;
Copy link
Member

Choose a reason for hiding this comment

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

There's an extra space after protected on some of these properties


/** For FormFieldControl. If any of the chips has focus, or the chip input has focus */
get focused() {
return !!this.chips.find((chip) => chip._hasFocus) ||
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 use some instead of find and you won't need the !!

return this._chipInput.placeholder;
} else {
return this._placeholder;
}
Copy link
Member

Choose a reason for hiding this comment

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

Use a ternary?

return this._chipInput ? this._chipInput.placeholder : this._placeholder;

styleUrls: ['chips.css'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MdChipList implements AfterContentInit, OnDestroy {
export class MdChipList implements MdFormFieldControl<any>, ControlValueAccessor,
Copy link
Member

Choose a reason for hiding this comment

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

Could you file an issue to track sharing common code for role="listbox" components in cdk/listbox? It would be good to capture what in particular would belong there

} else if (chipIndex - 1 >= 0) {
this._keyManager.setActiveItem(chipIndex - 1);
}
protected _updateKeyManager(chip: MdChip) {
Copy link
Member

Choose a reason for hiding this comment

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

Add function description?


_setSelectionByValue(value: any, isUserInput: boolean = true) {
this._clearSelection();
this.chips.forEach((chip) => chip.deselect());
Copy link
Member

Choose a reason for hiding this comment

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

Nit: don't need parenthesis around chip
(in any arrow function where the type is implicit)

_setSelectionByValue(value: any, isUserInput: boolean = true) {
this._clearSelection();
this.chips.forEach((chip) => chip.deselect());
const isArray = Array.isArray(value);
Copy link
Member

Choose a reason for hiding this comment

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

I wouldn't add the extra isArray variable here and would just use isArray() in the if block

*/
private _selectValue(value: any, isUserInput: boolean = true): MdChip | undefined {

const correspondingChip = this.chips.find((chip: MdChip) => {
Copy link
Member

Choose a reason for hiding this comment

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

The MdChip type should be implicit

@tinayuangao
Copy link
Contributor Author

Comments addressed. Added breaking changes to description.

For focus, user can set tabIndex to -1 to default focus on chip input.

<md-chip-list [tabIndex]="-1" #chipList> 
  <md-chip>chip one</md-chip>
  <input mdChipInputFor="chipList">
</md-chip-list>

And user can use left and right arrow keys to navigate to chips when tabIndex = -1.

Copy link
Contributor

@kara kara left a comment

Choose a reason for hiding this comment

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

Typos and a few questions


export interface MdChipInputEvent {
input: HTMLInputElement;
value: string;
}

/**
* A material design chips input component working with chip list component.
* To be placed inside md-form-field. Can be placed inside <md-chip-list> or outside <md-chip-list>.
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Use < > with md-form-field, since we are using them with md-chip-list later in the comment

}
protected _selected: boolean = false;

/** The value of the chip. Default to the content inside <md-chip> tags. */
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo: Default -> Defaults

import {MdFormFieldControl} from '../form-field/form-field-control';


let nextUniqueId = 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

Comment for this?


protected _value: any;

/** Placeholder for the chip list. Alternatively, placeholder can set to MdChipInput */
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo: can set to -> can be set on

@@ -90,3 +91,8 @@ $mat-chips-chip-margin: 8px;
margin-left: $mat-chip-remove-margin-after;
}
}

input.mat-chip-input {
width: 150px;
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be a Sass variable?

if (this._isValidIndex(chipIndex)) {
if (chip._hasFocus) {
// Check whether the chip is the last item
if (chipIndex < this.chips.length - 1) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: we could clarify this comment. The check is really for whether the chip is not the last item, right? I had to read it a few times before I realized it was doing the opposite

const isSubmitted = (this._parentFormGroup && this._parentFormGroup.submitted) ||
(this._parentForm && this._parentForm.submitted);

return !!(isInvalid && (isTouched || isSubmitted));
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks pretty similar to the select's default error state here: https://github.com/angular/material2/blob/master/src/lib/select/select.ts#L678

Could we share it somewhere? There is a default errorStateMatcher - could we update it to add the control existence checks?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We have #6741 to track sharing common code.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, good to know, thanks.


/**
* Whether or not this chip is selectable. When a chip is not selectable,
* it's selected state is always ignored.
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo: it's -> its

@Output() change: EventEmitter<MdChipListChange> = new EventEmitter<MdChipListChange>();

/**
* Event that emits whenever the raw value of the select changes. This is here primarily
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo: select -> chip list

// A different comparator means the selection could change.
this._initializeSelection();
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we share more code with the select here? Maybe a mixin that adds compareWith and default compareWith?

@kara kara removed their assignment Aug 31, 2017
@tinayuangao
Copy link
Contributor Author

Thanks for review. Comments addressed. Please take another look. Thanks!

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.

@tinayuangao can you add the deprecated message to the commit message? That's what the changelog script picks up

Might be easiest to squash

Does chips.md need to be updated at all for this change?

/**
* A material design chips input component working with chip list component.
* To be placed inside <md-form-field>. Can be placed inside <md-chip-list> or outside
* <md-chip-list>.
Copy link
Member

Choose a reason for hiding this comment

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

How about

Directive that adds chip-specific behaviors to an input element inside <md-form-field>.
May be placed inside or outside of an <md-chip-list>.

* <md-chip>Chip 2<md-chip>
* <input mdChipInputFor="chipList">
* </md-chip-list>
* </md-form-field>
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 it's better to leave the examples in the docs / live examples, since the generated API don't wont give this a nice visual treatment

'[attr.aria-describedby]': '_ariaDescribedby || null',
'[attr.aria-required]': 'required.toString()',
'[attr.aria-disabled]': 'disabled.toString()',
'[attr.aria-invalid]': 'errorState',
Copy link
Member

Choose a reason for hiding this comment

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

Does the component support multiple selection? If not, it should have aria-multiselectable="false"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Now it only support multiple selection.

I think I should change it to support single selection so we can share more common code with select component.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added single selection support to chip list. Please take another look. Thanks!

/** Comparison function to specify which option is displayed. Defaults to object equality. */
private _compareWith = (o1: any, o2: any) => o1 === o2;

get selected(): MdChip[] {
Copy link
Member

Choose a reason for hiding this comment

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

Add description?

return this._chipInput ? this._chipInput.placeholder : this._placeholder;
}

/** For FormFieldControl. If any of the chips has focus, or the chip input has focus */
Copy link
Member

Choose a reason for hiding this comment

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

/** Whether any chips or the mdChipInput inside of this chip-list has focus. */

return (!this._chipInput || this._chipInput.empty) && this.chips.length === 0;
}

/** For FormFieldControl. The disabled is not depend on chip input */
Copy link
Member

Choose a reason for hiding this comment

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

/** Whether this chip-list is disabled. */

get disabled() { return this.ngControl ? this.ngControl.disabled : this._disabled; }
set disabled(value: any) { this._disabled = coerceBooleanProperty(value); }

get errorState(): boolean {
Copy link
Member

@jelbourn jelbourn Aug 31, 2017

Choose a reason for hiding this comment

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

Add description?

}
}

_onBlur() {
Copy link
Member

Choose a reason for hiding this comment

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

It's weird that there's a _blur() and an _onBlur()- could the names be more specific? Function descriptions would also help

if (!this.disabled) {
if (this._chipInput) {
// Check if focus moved to chip input. If not, do real on blur
setTimeout(() => {
Copy link
Member

Choose a reason for hiding this comment

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

Add comment for why this needs to be a timeout?

}

_blur() {
if (!this.disabled) {
Copy link
Member

Choose a reason for hiding this comment

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

Would be good to have a comment that explains what this method is doing

@tinayuangao tinayuangao force-pushed the chip-input branch 2 times, most recently from dd2f0a0 to 234f0d2 Compare September 1, 2017 00:15
@tinayuangao
Copy link
Contributor Author

Comments addressed. Updated chips.md and commit message. Please take another look. Thanks!

@tinayuangao tinayuangao force-pushed the chip-input branch 2 times, most recently from 0cccb44 to 96ca320 Compare September 1, 2017 17:09
@jelbourn
Copy link
Member

jelbourn commented Sep 1, 2017

@tinayuangao prerender test looks to be failing now

@tinayuangao
Copy link
Contributor Author

@jelbourn Fixed prerender test. Please take a look. Thanks!

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.

LGTM, just needs rebase

@tinayuangao
Copy link
Contributor Author

Fixed focus & blur behavior for chip list. PTAL. Thanks!

Copy link
Contributor

@mmalerba mmalerba left a comment

Choose a reason for hiding this comment

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

LGTM

@tinayuangao tinayuangao added the P1 Impacts a large percentage of users; if a workaround exists it is partial or overly painful label Sep 14, 2017
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.

LGTM

@jelbourn jelbourn added the action: merge The PR is ready for merge by the caretaker label Sep 14, 2017
BREAKING CHANGES:
 - Deprecated: @output() select, and @output() deselect
 - Added @output() onSelectionChange = EventEmitter<MdChipSelectionChange>
@jelbourn jelbourn merged commit 7a42706 into angular:master Sep 18, 2017
@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 7, 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 P1 Impacts a large percentage of users; if a workaround exists it is partial or overly painful
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Chips & input: deal with multiple lines
5 participants