Skip to content

Conversation

@chang47
Copy link
Contributor

@chang47 chang47 commented Mar 23, 2018

Problem:
Currently, we don't have correct behavior for touch events for ComboBoxes and Splitbuttons.

Specifically:
For ComboBoxes when clicking on the input, we would select the text instead of expanding on the menu.

For SplitButtons, the menu button is too small and is hard to click to expand the menu.

Solution:
Add touch support so that when we click on the input box on the ComboBox, we would open the menu.

For the Base button, we added support for click so now that if we were to click on the split button we will always open the menu. This is fine, because the split button primary action should always be an option in the menu.

Validation:
I used a laptop with a touch screen and an iPad to test that we can now:

  1. expand the combo boxes when we touch the input box.
  2. open split button menus when we touch the split button container.

in Firefox, IE, Edge, Chrome. Safari

@chang47 chang47 requested a review from joschect as a code owner March 23, 2018 22:31
@chang47
Copy link
Contributor Author

chang47 commented Mar 23, 2018

@jspurlin

@jspurlin
Copy link
Contributor

@chang47 the bundle size needs to be updated

@chang47 chang47 requested a review from dzearing as a code owner March 23, 2018 23:19
return;
}
if (this._processingTouch) {
return this._onItemClick(item, ev);
Copy link
Member

Choose a reason for hiding this comment

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

can you add unit tests to validate the things you're changing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will do!

Copy link
Contributor Author

@chang47 chang47 Mar 30, 2018

Choose a reason for hiding this comment

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

Added unit tests, with some caveats if you look at another one of my comments.

Copy link
Contributor

@joschect joschect left a comment

Choose a reason for hiding this comment

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

It looks like pointer events are not yet supported by IOS. Maybe touch events should be used instead? https://caniuse.com/#search=pointer

Also since react currently doesn't support pointerevents it might make sense to go with touchstart as well.

@jspurlin
Copy link
Contributor

If anything we should fallback to touch events, but pointer events are standardized (since 2015), are supported practically everywhere, and we shouldn't implement this based off of outdated technology/APIs (e.g. touch events).

I also don't think the fact that react doesn't support pointerevents natively is a valid reason for not using pointer events.

}
}

public componentWillUnmount() {
Copy link
Member

Choose a reason for hiding this comment

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

why?


public componentDidMount() {
if (this._isSplitButton && this._splitButtonContainer) {
this._events.on(this._splitButtonContainer.value, 'pointerdown', this._onPointerDown, true);
Copy link
Member

Choose a reason for hiding this comment

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

can't you use react eventing? You should avoid using native events when possible.

Copy link
Contributor

@joschect joschect Mar 26, 2018

Choose a reason for hiding this comment

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

React does not support this yet, you could potentially mix it in with a ... but you won't get type safety. Based on this issue facebook/react#499 it looks like there was some disagreement within the react community as to whether or not pointer events should get added since they still aren't supported by IOS but someone could probably add that now.

"changes": [
{
"packageName": "office-ui-fabric-react",
"comment": "Added ontouch support to expand menus for split buttons and combo boxes",
Copy link
Member

Choose a reason for hiding this comment

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

SplitButton/ComboBox: added onTouch support for menu expansion.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated!

aria-setsize={ totalItemCount }
onKeyDown={ this._onSplitContainerItemKeyDown.bind(this, item) }
onClick={ this._executeItemClick.bind(this, item) }
onTouchStart={ this._onTouchStart.bind(this) }
Copy link
Contributor

Choose a reason for hiding this comment

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

We shouldn't need to bind this, you can use autobind on the function.

Also, why are we always setting this when we might also be setting onpointerdown? Couldn't this just be set in an else above if we know we can't use pointerdown... or is this always set just for automation?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

From what I know from merging all the changes, we moved away from using autobind and returned to using this, but I'm not sure of the reason. @joschect do you have any insights of why we moved away from this?

You are correct with why I had to set it here. It's for automation, we can't simulate the onTouchStart event any other way.

Copy link
Contributor

@jspurlin jspurlin Mar 30, 2018

Choose a reason for hiding this comment

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

Ah, good call. I didn't realize fabric moved away from autobind, looking at the changelog it states that you should use arrow functions instead of autobind so you could just do this._onTouchStart here (like onTouchStart={ this._onTouchStart }) and then in the function definition use an arrow function like private _onTouchStart = (ev: TouchEvent<HTMLElement>) => {

Copy link
Contributor

Choose a reason for hiding this comment

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

@chang47 we've moved away from autobind because it adds general code bloat and increases the size of functions

}

private _onTouchStart = (ev: TouchEvent<HTMLInputElement>): void => {
if (this._comboBoxWrapper.value && !('onpointerdown' in this._comboBoxWrapper)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@dzearing @joschect I had to add the onTouchStart event with an if check like this for split buttons, contextual menu split buttons, and combo boxes to support unit testing, because if I were to try and do something similar with addEventListener instead of adding this callback directly to a React component, the callback would never get called and it would not be unit testable.

Specifically, what I'm doing is: if onPointerDown is supported, than we don't do anything with onTouchStart and later in the componentDidMount, we'll add the pointerdown event, otherwise, we'll support onTouchStart and not add the pointerdown event.

This would be a temporary fix until React adds support for onPointerDown, but what are your thoughts on doing something like this? I was not able to think of a better solution. Thanks!

Copy link
Member

Choose a reason for hiding this comment

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

Got it.

I added other comments. let me know when you've resolved.

Copy link
Member

@dzearing dzearing left a comment

Choose a reason for hiding this comment

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

minor nits

@jspurlin
Copy link
Contributor

@chang47 you need to increase the bundle size you are .26kb over the current max

@jspurlin jspurlin dismissed dzearing’s stale review April 12, 2018 21:56

Dismissing outdated lock on this PR

}

public componentDidMount() {
if (this._isSplitButton && this._splitButtonContainer.value && 'onpointerdown' in this._splitButtonContainer.value) {
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps leave a comment here on why! That way the person re-writing the button later can understand your motivation.

// For split buttons, touching anywhere in the button should drop the dropdown, which should contain the primary action. This gives more hit target space for touch environments.

// Due to restraints caused by React 16 where we no can no longer
// call the ref values of children component with their own render
// function before componentDidMount we have to use this setTimeout
// hack to have access to ref. The correct approacj to solve this
Copy link
Member

Choose a reason for hiding this comment

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

"approach"

// call the ref values of children component with their own render
// function before componentDidMount we have to use this setTimeout
// hack to have access to ref. The correct approacj to solve this
// problem is to create a comonent for the split button and then
Copy link
Member

Choose a reason for hiding this comment

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

"component"

// function before componentDidMount we have to use this setTimeout
// hack to have access to ref. The correct approacj to solve this
// problem is to create a comonent for the split button and then
// have it add it's own event listener. This is logged on Issue 4522
Copy link
Member

Choose a reason for hiding this comment

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

its

This is logged in issue #4522

this._async.setTimeout(() => {
if (this._splitButtonContainers) {
this._splitButtonContainers.forEach((value: HTMLDivElement) => {
this._events.on(value, 'pointerdown', this._onPointerDown, true);
Copy link
Member

@dzearing dzearing Apr 12, 2018

Choose a reason for hiding this comment

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

hmm. In code earlier in this PR, you went to great lengths to validate that onPointerDown was "in" the value, and that the value was not falsey. Why are you not doing that here?

Also this feels REALLY gross. I think the lesson learned here is that the button should have been part of the item, not part of the menu...

What if the menu items change? Is this going to execute again? Are you going to miss registering new items? E.g. the items are lazy loaded.

@chang47
Copy link
Contributor Author

chang47 commented Apr 20, 2018

Created a separate PR to make the split button to be its own component so we can have better state management controls for touch: #4618

@dzearing dzearing closed this May 1, 2018
@dzearing dzearing reopened this May 1, 2018
@jspurlin
Copy link
Contributor

jspurlin commented May 1, 2018

@chang47 you need to bump the bundle size, you are .03KB over the current max size

@chang47
Copy link
Contributor Author

chang47 commented May 1, 2018

@jspurlin will do, just let me do some final validations.

@chang47 chang47 merged commit f526bfe into microsoft:master May 2, 2018
@microsoft microsoft locked as resolved and limited conversation to collaborators Aug 31, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants