-
-
Notifications
You must be signed in to change notification settings - Fork 32.4k
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
Conversation
@JessThrysoee I like the direction this PR is taking. The
Increasing the test coverage is one of our priority with the @Sharlaan I would love to have your feedback on this PR. Would that help with material-ui-superselectfield? |
Nice to see another alternative! At first glance, it doesnot help me much since i'm already quite ahead with features. Regarding 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. 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. |
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. |
in my project, i actually called your 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 got same linting issues, but there are 2 PRs fixing them in the PRs queue XD |
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. |
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 👍 . 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) => { |
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 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.
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.
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.
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.
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.
src/DropDownMenu/DropDownMenu.js
Outdated
maxHeight={maxHeight} | ||
desktop={true} | ||
value={value} | ||
onEscKeyDown={this.handleEscKeyDownMenu} | ||
style={menuStyle} | ||
listStyle={listStyle} | ||
onItemTouchTap={this.handleItemTouchTap} | ||
onChange={this.handleOnChange} |
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 have the following naming convention:
onChange={this.handleChange}
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 |
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.
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) { |
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 could avoid that function indirection, but I'm fine either way.
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.
OK. I'll add it inline.
} | ||
|
||
render() { | ||
const {values} = this.state; |
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.
Some would consider destructuration overkill here, I'm fine either way.
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.
OK
@@ -60,6 +62,14 @@ const SelectFieldPage = () => ( | |||
> | |||
<SelectFieldExampleError /> | |||
</CodeExample> | |||
|
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 avoid blank line in the render method of the components.
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.
OK
src/DropDownMenu/DropDownMenu.js
Outdated
@@ -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)) { |
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 is this logic for?
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.
When multiple is true, the dropdown shouldn't be closed on touch. We wan't to leave it open for further selections.
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.
From my own tests, that callback isn't called when the dropdown is touched. I think that it's a dead logic.
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 are right. The ClearFix component is covered by the Popover so no touch events are possible.
src/DropDownMenu/DropDownMenu.js
Outdated
@@ -274,15 +294,28 @@ class DropDownMenu extends Component { | |||
|
|||
handleItemTouchTap = (event, child, index) => { | |||
event.persist(); |
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 that we can move that line in the else
clause.
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 have moved it.
@@ -274,15 +294,28 @@ class DropDownMenu extends Component { | |||
|
|||
handleItemTouchTap = (event, child, index) => { |
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.
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 |
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 about asserting the state
before the deselect?
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.
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); |
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.
Sounds like an abstraction leak. Can't let the TextField untouched?
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.
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.
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 100% agree with the motivation. My point is about moving the piece of logic into the DropDownMenu component.
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.
Good point. I'll move it into DropDownMenu.
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.
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.
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.
@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, |
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 fine with that name
👍 . That might miss a unit test.
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.
Agree, that needs to be tested.
@JessThrysoee That's a great PR, good job so far. Sorry for not reviewing it earlier. |
I'm still having a problem with the SelectField.spec.js test. It runs successfully with It looks like |
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. |
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 are really close 🎉 .
src/DropDownMenu/DropDownMenu.js
Outdated
@@ -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)) { |
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.
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); |
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.
@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.
@JessThrysoee Thanks a lot for your effort! |
@JessThrysoee Thanks for closing the all-time most requested feature! 🎉 https://github.com/callemall/material-ui/issues?q=is%3Aissue+sort%3Areactions-%2B1-desc |
@oliviertassinari @mbrookes Glad to have contributed. Thank you guys for creating and maintaining such a high quality project! |
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? |
I'm interested too with @patrickml question, this is the reason i made my SuperSelectField. |
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
Menu
s multi select support available inSelectField
andDropDownMenu
. 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.