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

[SelectField] [DropDownMenu] Support multi select #6165

Merged
merged 22 commits into from
Mar 11, 2017
Merged

[SelectField] [DropDownMenu] Support multi select #6165

merged 22 commits into from
Mar 11, 2017

Conversation

JessThrysoee
Copy link

@JessThrysoee JessThrysoee commented Feb 16, 2017

Multi select in SelectField is requested in #1956, but progress doesn't seem to have been made (I'm new to material-ui so I might just be uninformed :-).

To get the ball rolling, this PR simply makes Menus multi select support available in SelectField and DropDownMenu. A demo of what it looks like can be seen here: http://thrysoee.dk/material-ui/

I haven't looked into updating docs/tests/... yet. I thought I'd hear what you guys think before going any further.

@muibot muibot added PR: Needs Review PR: needs revision The pull request can't be merged. More details is available on the code review or fails in the CI and removed PR: Needs Review labels Feb 16, 2017
@JessThrysoee JessThrysoee changed the title [SelectField] Add multi select to SelectField and DropDownMenu [SelectField] [DropDownMenu] Support multi select Feb 18, 2017
@oliviertassinari
Copy link
Member

oliviertassinari commented Feb 19, 2017

@JessThrysoee I like the direction this PR is taking. The multiple property name sounds better to the multi one used by react-select.

I haven't looked into updating docs/tests/... yet. I thought I'd hear what you guys think before going any further.

Increasing the test coverage is one of our priority with the next branch. So doing the same with the master would be great.

@Sharlaan I would love to have your feedback on this PR. Would that help with material-ui-superselectfield?

@oliviertassinari oliviertassinari added component: select This is the name of the generic UI component, not the React module! new feature New feature or request and removed PR: Needs Review labels Feb 19, 2017
@Sharlaan
Copy link

Sharlaan commented Feb 19, 2017

Nice to see another alternative!

At first glance, it doesnot help me much since i'm already quite ahead with features.

Regarding multiple i'm currently adding another prop to complement it : checkPosition (default: '') PropTypes.oneOf(['', 'left', 'right'])
Some people may not want the check mark, or want it as right icon.

Regarding datasource, what if dev wants to display a label different than its associated value ? (ie datasource as ArrayOf[Object])

Regarding the display of selections, this uses the classic one with joined values string + ellipsis.
What if user want to see full list ?
Also how to display those selections in a more fashion way ?

Finally, what's the difference between DropDown and SelectField components ? why not merge them into one same component ?

Hope it helps.

At the moment i'm struggling with ListItem's focusState to implement the keyboard gestures, and hoverColor/Ripple states.
Your thoughts ?

@JessThrysoee
Copy link
Author

Thanks for the feedback!

I agree that we shouldn't assume that the user want a check mark toggled, so this PR doesn't affect the MenuItems at all :-) It only makes Menu's multiple prop available and returns an array of values in onChange when multiple is true. It is up to the user what should be updated on the MenuItems.

Regarding datasource and display of selections: Yes, the user should absolutely be able to render the selection. I have just pushed a small change that adds an onChangeRenderer prop, so the user has full control over the rendering. I have updated the demo to show onChangeRenderer returning a Component instead of a string - http://thrysoee.dk/material-ui/ .

(This PR tries to be a small but useful increment of functionality)

BTW: The lint is currently failing on files unrelated to this PR.

@Sharlaan
Copy link

in my project, i actually called your onChangeRenderer as selectionsRenderer, up to you ;)

My comment about the check mark is not inherent to your PR, but to the MenuItem itself, which assumes the user want a check mark :s
This would need another PR in my opinion.

got same linting issues, but there are 2 PRs fixing them in the PRs queue XD

@JessThrysoee
Copy link
Author

Yes. selectionsRenderer is a better name. This is not an event handler so onChangeRenderer is misleading. I think maybe for this use the singular selectionRenderer is more in line with the 'value' prop.

Copy link
Member

@oliviertassinari oliviertassinari 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 👍 . Thanks for working on it. Aside from my comment, we gonna need some tests and a documentation example to get it merged.

@@ -274,15 +294,28 @@ class DropDownMenu extends Component {

handleItemTouchTap = (event, child, index) => {
Copy link
Member

@oliviertassinari oliviertassinari Feb 21, 2017

Choose a reason for hiding this comment

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

I haven't realized that the Menu component already handles a multiple property.

With that in mind, why is onItemTouchTap triggering the onChange event. Should it be decoupled and handled aside? I mean in the handleOnChange function.

I feel like we have quite some logic duplication.

Copy link
Author

Choose a reason for hiding this comment

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

Yes the idea is to expose Menu's existing multiple functionality.

Menu has two callbacks onChange and onItemTouchTap. In the current master branch Menu's onItemTouchTab is propagated up through DropDownMenu's and SelectField's onChange callbacks. So to avoid breaking existing clients I thought that was best left unchanged.

Menu's onChange is already using the semantics that if multiple is true then onChange is called with arrays, so I did the same for SelectField and DropDownMenu.

The logic duplication is mostly in Menu. If breaking changes could be made then I think Menu's onItemTouchTab should be removed in favor of an updated onChange with a richer set of parameters.

Copy link
Member

Choose a reason for hiding this comment

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

The logic duplication is mostly in Menu. If breaking changes could be made then I think Menu's onItemTouchTab should be removed in favor of an updated onChange with a richer set of parameters.

I agree but I would definitely avoid introducing breaking changes on the master. We have the next branch to move forward. That current logic sounds like a good tradeoff for now.

maxHeight={maxHeight}
desktop={true}
value={value}
onEscKeyDown={this.handleEscKeyDownMenu}
style={menuStyle}
listStyle={listStyle}
onItemTouchTap={this.handleItemTouchTap}
onChange={this.handleOnChange}
Copy link
Member

@oliviertassinari oliviertassinari Feb 21, 2017

Choose a reason for hiding this comment

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

We have the following naming convention:

onChange={this.handleChange}

@oliviertassinari oliviertassinari removed the PR: needs revision The pull request can't be merged. More details is available on the code review or fails in the CI label Feb 22, 2017
@JessThrysoee
Copy link
Author

JessThrysoee commented Feb 22, 2017

I'm having trouble writing tests for DropDownMenu. I mountWithContext and simulate('touchTap') to open the Popover, but enzyme debug tells me that there are no MenuItems to select.

<DropDownMenu >
  <div >
    <ClearFix >
    </ClearFix>
    <Popover  open={true} >
      <div >
        <EventListener target="window" />
        <RenderToLayer open={true} />
      </div>
    </Popover>
  </div>
</DropDownMenu>

I suspect it has something to do with RenderToLayer waiting until componentDidMount to call its renderLayer method? Any hints to how I can test selecting a MenuItem would be greatly appreciated!

@oliviertassinari Would it be sufficient to write multi select tests for Menu given that most of the functionality is handled there?

@muibot muibot added PR: needs revision The pull request can't be merged. More details is available on the code review or fails in the CI PR: Needs Review and removed PR: Needs Review labels Feb 24, 2017
@oliviertassinari oliviertassinari removed the PR: needs revision The pull request can't be merged. More details is available on the code review or fails in the CI label Mar 5, 2017
Copy link
Member

@oliviertassinari oliviertassinari left a comment

Choose a reason for hiding this comment

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

Any hints to how I can test selecting a MenuItem would be greatly appreciated!

I think that it's linked to the Popover component rendering the component outside of his DOM parent.
The classname workaround is clever. #6290 is also looking for a better solution.


handleChange = (event, index, values) => this.setState({values});

menuItems(values) {
Copy link
Member

Choose a reason for hiding this comment

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

We could avoid that function indirection, but I'm fine either way.

Copy link
Author

Choose a reason for hiding this comment

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

OK. I'll add it inline.

}

render() {
const {values} = this.state;
Copy link
Member

Choose a reason for hiding this comment

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

Some would consider destructuration overkill here, I'm fine either way.

Copy link
Author

Choose a reason for hiding this comment

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

OK

@@ -60,6 +62,14 @@ const SelectFieldPage = () => (
>
<SelectFieldExampleError />
</CodeExample>

Copy link
Member

Choose a reason for hiding this comment

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

We avoid blank line in the render method of the components.

Copy link
Author

Choose a reason for hiding this comment

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

OK

@@ -241,7 +261,7 @@ class DropDownMenu extends Component {

handleTouchTapControl = (event) => {
event.preventDefault();
if (!this.props.disabled) {
if (!this.props.disabled && !(this.props.multiple && this.state.open)) {
Copy link
Member

Choose a reason for hiding this comment

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

What is this logic for?

Copy link
Author

Choose a reason for hiding this comment

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

When multiple is true, the dropdown shouldn't be closed on touch. We wan't to leave it open for further selections.

Copy link
Member

Choose a reason for hiding this comment

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

From my own tests, that callback isn't called when the dropdown is touched. I think that it's a dead logic.

Copy link
Author

Choose a reason for hiding this comment

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

You are right. The ClearFix component is covered by the Popover so no touch events are possible.

@@ -274,15 +294,28 @@ class DropDownMenu extends Component {

handleItemTouchTap = (event, child, index) => {
event.persist();
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 that we can move that line in the else clause.

Copy link
Author

Choose a reason for hiding this comment

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

I have moved it.

@@ -274,15 +294,28 @@ class DropDownMenu extends Component {

handleItemTouchTap = (event, child, index) => {
Copy link
Member

Choose a reason for hiding this comment

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

The logic duplication is mostly in Menu. If breaking changes could be made then I think Menu's onItemTouchTab should be removed in favor of an updated onChange with a richer set of parameters.

I agree but I would definitely avoid introducing breaking changes on the master. We have the next branch to move forward. That current logic sounds like a good tradeoff for now.

TestUtils.Simulate.touchTap(item1);
TestUtils.Simulate.touchTap(item2);
TestUtils.Simulate.touchTap(item3);
TestUtils.Simulate.touchTap(item1); // deselect
Copy link
Member

Choose a reason for hiding this comment

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

What about asserting the state before the deselect?

Copy link
Author

Choose a reason for hiding this comment

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

OK

@@ -105,7 +105,7 @@ const getStyles = (props, context, state) => {
* @returns True if the string provided is valid, false otherwise.
*/
function isValid(value) {
return value !== '' && value !== undefined && value !== null;
return value !== '' && value !== undefined && value !== null && !(Array.isArray(value) && value.length === 0);
Copy link
Member

Choose a reason for hiding this comment

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

Sounds like an abstraction leak. Can't let the TextField untouched?

Copy link
Author

Choose a reason for hiding this comment

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

Yes. I agree that it look a little odd, but I added it so SelectField's value can be initialized to an empty array (as well as null) which feels more natural when multiple is true. Without this test TextField always thinks it has a value and hintText and floatingLabelText doesn't behave as expected.

Copy link
Member

Choose a reason for hiding this comment

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

I 100% agree with the motivation. My point is about moving the piece of logic into the DropDownMenu component.

Copy link
Author

Choose a reason for hiding this comment

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

Good point. I'll move it into DropDownMenu.

Copy link
Author

Choose a reason for hiding this comment

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

Hmm. TextField is reaching down into the child DropDownMenu to read its value, so I don't see a good way to avoid the empty array check in TextField.

Copy link
Member

Choose a reason for hiding this comment

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

@JessThrysoee Oh, I remember that. Well, I'm find keeping that hack as we are rewriting the lib on the next branch.

* it wasn't already selected) or omitted (if it was already selected).
* Otherwise, the `value` of the menu item.
*/
selectionRenderer: PropTypes.func,
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 fine with that name 👍 . That might miss a unit test.

Copy link
Author

Choose a reason for hiding this comment

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

Agree, that needs to be tested.

@oliviertassinari
Copy link
Member

@JessThrysoee That's a great PR, good job so far. Sorry for not reviewing it earlier.
We are close from getting it merged 🎉 .

@oliviertassinari oliviertassinari added PR: needs revision The pull request can't be merged. More details is available on the code review or fails in the CI and removed PR: Needs Review labels Mar 6, 2017
@muibot muibot added the PR: out-of-date The pull request has merge conflicts and can't be merged label Mar 6, 2017
@JessThrysoee
Copy link
Author

I'm still having a problem with the SelectField.spec.js test. It runs successfully with npm run test:unit -- -c SelectField but fails with ./scripts/run-travis-tests.sh

It looks like TestUtils.Simulate.touchTap(item1); isn't working during the travis test run for SelectField.spec.js, but on the other hand it works fine in DropDownMenu.spec.js

@JessThrysoee
Copy link
Author

Added an example of using selectionRenderer to the docs.

I think the PR is in good shape now, so I'll wait for review (or merge :) before doing anything more.

Copy link
Member

@oliviertassinari oliviertassinari left a comment

Choose a reason for hiding this comment

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

We are really close 🎉 .

@@ -241,7 +261,7 @@ class DropDownMenu extends Component {

handleTouchTapControl = (event) => {
event.preventDefault();
if (!this.props.disabled) {
if (!this.props.disabled && !(this.props.multiple && this.state.open)) {
Copy link
Member

Choose a reason for hiding this comment

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

From my own tests, that callback isn't called when the dropdown is touched. I think that it's a dead logic.

@@ -105,7 +105,7 @@ const getStyles = (props, context, state) => {
* @returns True if the string provided is valid, false otherwise.
*/
function isValid(value) {
return value !== '' && value !== undefined && value !== null;
return value !== '' && value !== undefined && value !== null && !(Array.isArray(value) && value.length === 0);
Copy link
Member

Choose a reason for hiding this comment

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

@JessThrysoee Oh, I remember that. Well, I'm find keeping that hack as we are rewriting the lib on the next branch.

The ClearFix component is covered by the Popover so it is not possible
to interact with it.
@oliviertassinari oliviertassinari added PR: Review Accepted and removed PR: Needs Review PR: needs revision The pull request can't be merged. More details is available on the code review or fails in the CI labels Mar 8, 2017
@oliviertassinari oliviertassinari merged commit 20325bc into mui:master Mar 11, 2017
@oliviertassinari
Copy link
Member

@JessThrysoee Thanks a lot for your effort!

@mbrookes
Copy link
Member

@JessThrysoee Thanks for closing the all-time most requested feature! 🎉

https://github.com/callemall/material-ui/issues?q=is%3Aissue+sort%3Areactions-%2B1-desc

@JessThrysoee
Copy link
Author

@oliviertassinari @mbrookes Glad to have contributed. Thank you guys for creating and maintaining such a high quality project!

@JessThrysoee JessThrysoee deleted the multiselectfield branch March 12, 2017 07:52
@patrickml
Copy link

This is really nice! What would it take to add an autocomplete search to something like this? I'd assume it'd just be a simple filter?

@Sharlaan
Copy link

I'm interested too with @patrickml question, this is the reason i made my SuperSelectField.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: select This is the name of the generic UI component, not the React module! new feature New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants