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

Prevent default when handling keydown events #836

Merged
merged 3 commits into from
Jan 26, 2017

Conversation

T-Hugs
Copy link
Contributor

@T-Hugs T-Hugs commented Jan 18, 2017

Pull request checklist

Description of changes

Adds preventDefault() when handling keydown event on a ContextualMenu. This is necessary so that event handlers on parent elements know whether or not the event was handled. In our case, an arrow key pressed in a menu might not have an effect (e.g. there is no right sub-menu), in which case the parent needs to handle the user's intent (move to the next horizontal menu item, for example).

Focus areas to test

(optional)

@msftclas
Copy link

Hi @trevorsg, I'm your friendly neighborhood Microsoft Pull Request Bot (You can call me MSBOT). Thanks for your contribution!


It looks like you're a Microsoft contributor (Trevor Gau). If you're full-time, we DON'T require a Contribution License Agreement. If you are a vendor, please DO sign the electronic Contribution License Agreement. It will take 2 minutes and there's no faxing! https://cla.microsoft.com.

TTYL, MSBOT;

@T-Hugs
Copy link
Contributor Author

T-Hugs commented Jan 18, 2017

By the way, this PR is submitted on behalf of Craig Harry, who cannot log into GitHub because he does not have a phone for 2FA.

@@ -459,6 +459,7 @@ export class ContextualMenu extends BaseComponent<IContextualMenuProps, IContext

if (ev.which === openKey) {
this._onItemSubMenuExpand(item, ev.currentTarget as HTMLElement);
ev.preventDefault();
Copy link
Member

Choose a reason for hiding this comment

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

shouldn't this also have ev.stopPropagation() ?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think preventDefault makes sense, maybe for logging or something like that? Where it should continue to propagate but no other native actions should take place?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think from the description it should propagate so that the parent can handle

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No. I'd argue that stopPropagation is almost never what you want. The parent should look at the defaultPrevented property on the event object in most cases to determine if action should be taken. If propagation is stopped, the parent doesn't even get that choice.

@joschect You mention native actions, but is that what you mean? The preventDefault() call obviously prevents native actions being handled, but more importantly parent event handlers can look at the defaultPrevented property on the event to see if the default action has already been taken by a child handler.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, preventing native actions is all I meant.

Copy link
Contributor

Choose a reason for hiding this comment

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

I talked with @dzearing and I agree with him that it should have both preventDefault and stopPropagation. The code should prevent the browser from handling the event as well as preventing it from getting to other handlers.

Copy link
Member

@dzearing dzearing Jan 18, 2017

Choose a reason for hiding this comment

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

I think it would be probably make the most sense to add it. It would at least be consistent with other places where we handle events.

I'd be interested in how it's cleaner to avoid stopPropagation, it's a bit unclear to me, I might be naive here tho! I thought about the purposes of the 2 methods:

preventDefault: Prevents the default browser behavior. It is intended to prevent things like scrollbars moving, buttons submitting forms, and anchors from navigating.

stopPropagation: Prevents the event from bubbling, which avoids user code. It doesn't affect browser behaviors; it simply avoids user code from executing. This is important when an event has been handled and it is intended for no other actions to handle the event.

So interpretting defaultPrevented as a signal for avoiding user code might be presumptuous or wrong.

Again, possible to watch for defaultPrevented, it just may not be the right signal indicating that user code executed an action and subsequent user code actions should be avoided.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The API gives us two methods, preventDefault and stopPropagation. Given the above argument, it is hard to imagine a scenario where it would be useful to call one of the two methods, but not both. This makes me think they are not necessarily intended to be called as a pair in all/most cases.

Here's an example scenario where it might be useful for a parent to react to events that have been handled by the child:

Say you have a ContextMenu that is configured to hide when the user "clicks away". That is, the user clicks any part of the UI that is not part of the menu. The user might click a button or some other element that handles the click. But you still want the menu to close. If the event propagation was stopped, that event would not be able to make it up to the document triggering the closing of the ContextMenu.

Anyway, I'm not trying to be argumentative here. I think calling both preventDefault and stopPropagation is the more common pattern, so it may be best to follow that.

Copy link
Contributor

@mdahamiwal mdahamiwal Jan 19, 2017

Choose a reason for hiding this comment

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

I don't think we should blindly use both of them. stopPropagation can be hazardous as evident from the example by @trevorsg. Infact, we just fixed a bug in Dropdown to avoid stop propagating event if it doesn't handle the keypress and give chance to parent components (dialog) to act on it.
preventDefault is mostly used when we want to avoid browser default behavior and handle it ourselves. As in this PR, with custom context menu we don't want browsers to open its default context menu for some element. Or say, we want to avoid link click navigation and handle it ourselves. That said, preventDefault and defaultPrevented can be used as an alternative to stopPropagation. It is explained at dangers of stopping event propagation as one of the best practices to follow.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for finding that article, Manish. It organizes my thoughts on the subject very nicely!

@cliffkoh
Copy link
Contributor

Hi @trevorsg, to get this checked in, could you just run rush change to generate a change file?

@T-Hugs
Copy link
Contributor Author

T-Hugs commented Jan 25, 2017

@cliffkoh Is there a doc I'm supposed to read about how to do that? I npm installed @microsoft/rush, then ran rush change, but I get this error...

C:\dev\office-ui-fabric-react\packages\office-ui-fabric-react\src\components\ContextualMenu>rush change

Rush Multi-Package Build Tool 1.5.1

Found configuration in C:\dev\office-ui-fabric-react\rush.json

events.js:160
      throw er; // Unhandled 'error' event
      ^

Error: This socket is closed
    at WriteStream.Socket._writeGeneric (net.js:683:19)
    at WriteStream.Socket._write (net.js:734:8)
    at doWrite (_stream_writable.js:334:12)
    at writeOrBuffer (_stream_writable.js:320:5)
    at WriteStream.Writable.write (_stream_writable.js:247:11)
    at WriteStream.Socket.write (net.js:661:40)
    at Console.warn (console.js:51:16)
    at RushCommandLineParser.trapErrors (C:\Users\trgau\AppData\Roaming\npm\node_modules\@microsoft\rush\lib\actions\RushCommandLineParser.js:72:25)
    at RushCommandLineParser.onExecute (C:\Users\trgau\AppData\Roaming\npm\node_modules\@microsoft\rush\lib\actions\RushCommandLineParser.js:57:14)
    at RushCommandLineParser.CommandLineParser.execute (C:\Users\trgau\AppData\Roaming\npm\node_modules\@microsoft\rush\node_modules\@microsoft\ts-command-line\lib\CommandLineParser.js:76:14)

@cschleiden
Copy link
Contributor

@trevorsg I just run npm run change from the root

@T-Hugs
Copy link
Contributor Author

T-Hugs commented Jan 25, 2017

@cschleiden I still get the same error. Is there an order of operations? I made a commit then ran npm run change - am I missing a step?

@cschleiden
Copy link
Contributor

@trevorsg Hm, not that I'm aware of. That's how it worked for me last night :)

@cliffkoh
Copy link
Contributor

@trevorsg, for the issue you encountered, @qinweiz and @pgonzal can help. An alternative is to just manually create a change file in common/changes, something like this: https://github.com/OfficeDev/office-ui-fabric-react/blob/master/common/changes/separate-button-sass-fixes_2017-01-25-16-36.json

@octogonz
Copy link

Can you run rush -d change and paste the console output?

Also, do you have a repro? (e.g. enlist in branch X and run these commands, something like that?) I wasn't following this thread, so I don't have much context, but if there's a Rush bug, we're interested.

@trevorsg

@dzearing dzearing merged commit 179d155 into microsoft:master Jan 26, 2017
@T-Hugs
Copy link
Contributor Author

T-Hugs commented Jan 26, 2017

@pgonzal
Here's the output with -d:

C:\dev\office-ui-fabric-react>rush -d change

Rush Multi-Package Build Tool 1.5.1


events.js:160
      throw er; // Unhandled 'error' event
      ^

Error: write EINVAL
    at exports._errnoException (util.js:1022:11)
    at WriteStream.Socket._writeGeneric (net.js:715:26)
    at WriteStream.Socket._write (net.js:734:8)
    at doWrite (_stream_writable.js:334:12)
    at writeOrBuffer (_stream_writable.js:320:5)
    at WriteStream.Writable.write (_stream_writable.js:247:11)
    at WriteStream.Socket.write (net.js:661:40)
    at Object.execSync (child_process.js:526:20)
    at Function.VersionControl.getChangedFolders (C:\Users\trgau\AppData\Roaming\npm\node_modules\@microsoft\rush\node_modules\@microsoft\rush-lib\lib\utilities\VersionControl.js:8:36)
    at ChangeAction._getChangedPackageNames (C:\Users\trgau\AppData\Roaming\npm\node_modules\@microsoft\rush\lib\actions\ChangeAction.js:99:56)

Repro:

  1. Clone this repo
  2. CD into repo
  3. npm i
  4. npm i -g @microsoft/rush
  5. <make change>
  6. git commit -am "foo"
  7. rush change

Let me know if I can get you any more info.

@octogonz
Copy link

octogonz commented Jan 26, 2017

@trevorsg What was step #5? :-) I tried commiting a trivial change to DetailsList.ts and then running "rush change", but it works fine for me.

Do I need to switch to a certain branch? Also, what OS are you using?

@T-Hugs
Copy link
Contributor Author

T-Hugs commented Jan 26, 2017

@pgonzal Updated :) I forgot to escape the < and >

@octogonz
Copy link

@trevorsg Thanks, however I still can't repro the bug. I tried to find your branch trevorsg:patch-1, but it looks like it got squashed when the PR was merged. :-(

@cliffkoh
Copy link
Contributor

cliffkoh commented Jan 26, 2017

@trevorsg 1 thought - what version of node are you running on? (node -v), and what OS? :)

@T-Hugs
Copy link
Contributor Author

T-Hugs commented Jan 26, 2017

@cliffkoh I was on 7.1.0. Looks like I was hitting something related to this issue: nodejs/node#9542

I updated to 7.2.1 and now everything is working. Thanks @pgonzal for helping debug.

@octogonz
Copy link

@cliffkoh @dzearing I noticed that your rush.json file has this line:

  "nodeSupportedVersionRange": ">=4.3.0",

I would suggest to restrict it to something like this:

"nodeSupportedVersionRange": ">=6.9.0 <7.0.0",

Supporting a wide range of NodeJS versions has been a nuisance for us in the past. For our projects, we generally require people to be on the recent LTS version range. NodeJS rolls them pretty often, so you won't be missing any killer features, and it's hardly any hassle if people are using nodist or nvm (which I strongly recommend).

@cliffkoh
Copy link
Contributor

@pgonzal Created an issue to track this, will get to it later today or tomorrow. Thanks!

@dzearing
Copy link
Member

@pgonzal thanks!

aleksandrjPersonal pushed a commit to aleksandrjPersonal/office-ui-fabric-react that referenced this pull request Feb 17, 2017
…ft#836)

* Prevent default when handling keydown events

* Update ContextualMenu.tsx
@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.

8 participants