From 292dd238e70400a16bebd000fc4364c42d3effe7 Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Thu, 30 May 2013 18:14:33 -0700 Subject: [PATCH 1/4] Add new onInput event 'input' is supported in IE9+ and all other browsers according to https://developer.mozilla.org/en-US/docs/Web/API/window.oninput Test Plan: Modified ballmer-peak example to use onInput instead of onKeyUp and tested that it works properly on latest Chrome. --- src/core/ReactEvent.js | 1 + src/event/EventConstants.js | 1 + src/eventPlugins/SimpleEventPlugin.js | 7 +++++++ 3 files changed, 9 insertions(+) diff --git a/src/core/ReactEvent.js b/src/core/ReactEvent.js index dd2058f368993..702c8816c3c2c 100644 --- a/src/core/ReactEvent.js +++ b/src/core/ReactEvent.js @@ -211,6 +211,7 @@ function listenAtTopLevel(touchNotMouse) { trapBubbledEvent(topLevelTypes.topKeyUp, 'keyup', mountAt); trapBubbledEvent(topLevelTypes.topKeyPress, 'keypress', mountAt); trapBubbledEvent(topLevelTypes.topKeyDown, 'keydown', mountAt); + trapBubbledEvent(topLevelTypes.topInput, 'input', mountAt); trapBubbledEvent(topLevelTypes.topChange, 'change', mountAt); trapBubbledEvent( topLevelTypes.topDOMCharacterDataModified, diff --git a/src/event/EventConstants.js b/src/event/EventConstants.js index 0ce7820a8ee5b..0af7b9d0b29b1 100644 --- a/src/event/EventConstants.js +++ b/src/event/EventConstants.js @@ -32,6 +32,7 @@ var topLevelTypes = keyMirror({ topDOMCharacterDataModified: null, topDoubleClick: null, topFocus: null, + topInput: null, topKeyDown: null, topKeyPress: null, topKeyUp: null, diff --git a/src/eventPlugins/SimpleEventPlugin.js b/src/eventPlugins/SimpleEventPlugin.js index 7591ee3542681..0453fde60a5cc 100644 --- a/src/eventPlugins/SimpleEventPlugin.js +++ b/src/eventPlugins/SimpleEventPlugin.js @@ -108,6 +108,12 @@ var SimpleEventPlugin = { captured: keyOf({onKeyDownCapture: true}) } }, + input: { + phasedRegistrationNames: { + bubbled: keyOf({onInput: true}), + captured: keyOf({onInputCapture: true}) + } + }, focus: { phasedRegistrationNames: { bubbled: keyOf({onFocus: true}), @@ -220,6 +226,7 @@ SimpleEventPlugin.topLevelTypesToAbstract = { topKeyUp: SimpleEventPlugin.abstractEventTypes.keyUp, topKeyPress: SimpleEventPlugin.abstractEventTypes.keyPress, topKeyDown: SimpleEventPlugin.abstractEventTypes.keyDown, + topInput: SimpleEventPlugin.abstractEventTypes.input, topFocus: SimpleEventPlugin.abstractEventTypes.focus, topBlur: SimpleEventPlugin.abstractEventTypes.blur, topScroll: SimpleEventPlugin.abstractEventTypes.scroll, From 2467c0e6519676b8200ecc71303e4452dccde080 Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Thu, 30 May 2013 18:16:27 -0700 Subject: [PATCH 2/4] Update examples to use onInput instead of onKeyUp onInput has the advantage that it responds to repeated key events before onKeyUp and is called when modifying the input without the keyboard (such as pasting with the mouse). Test Plan: Opened the ballmer-peak example and docs homepage in Chrome and checked that both examples update whenever the text is changed. --- docs/_js/examples/markdown.js | 4 ++-- examples/ballmer-peak/example.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/_js/examples/markdown.js b/docs/_js/examples/markdown.js index ec4363f56ca08..ec70b95503a87 100644 --- a/docs/_js/examples/markdown.js +++ b/docs/_js/examples/markdown.js @@ -11,14 +11,14 @@ var MarkdownEditor = React.createClass({\n\ getInitialState: function() {\n\ return {value: 'Type some *markdown* here!'};\n\ },\n\ - handleKeyUp: React.autoBind(function() {\n\ + handleInput: React.autoBind(function() {\n\ this.setState({value: this.refs.textarea.getDOMNode().value});\n\ }),\n\ render: function() {\n\ return (\n\
\n\

Input

\n\ - \n\

Output

\n\ diff --git a/examples/ballmer-peak/example.js b/examples/ballmer-peak/example.js index 1ca43989a086d..0a92b7c17dbac 100644 --- a/examples/ballmer-peak/example.js +++ b/examples/ballmer-peak/example.js @@ -33,7 +33,7 @@ var BallmerPeakCalculator = React.createClass({

Compute your Ballmer Peak:

If your BAC is{' '} - + {', '}then {pct} of your lines of code will have bugs.

From 580e8f0dbbbb7963c27fee42fd843c6d2a89e2ca Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Sat, 1 Jun 2013 16:53:07 -0700 Subject: [PATCH 3/4] Simulate input event instead of relying on native Test Plan: With the ballmer-peak example (modified to use input), tested that the percentage updates when adding or deleting text in the field on Chrome and IE9. After adding es5-shim and es5-sham to the ballmer-peak page, IE8 works properly too. --- src/core/ReactDefaultInjection.js | 4 +- src/core/ReactEvent.js | 4 +- src/event/EventConstants.js | 2 + src/eventPlugins/DefaultEventPluginOrder.js | 1 + src/eventPlugins/InputEventPlugin.js | 99 +++++++++++++++++++++ src/eventPlugins/SimpleEventPlugin.js | 7 -- 6 files changed, 108 insertions(+), 9 deletions(-) create mode 100644 src/eventPlugins/InputEventPlugin.js diff --git a/src/core/ReactDefaultInjection.js b/src/core/ReactDefaultInjection.js index 75044d849ffff..8cecbccd49c52 100644 --- a/src/core/ReactDefaultInjection.js +++ b/src/core/ReactDefaultInjection.js @@ -24,6 +24,7 @@ var ReactDOMForm = require('ReactDOMForm'); var DefaultEventPluginOrder = require('DefaultEventPluginOrder'); var EnterLeaveEventPlugin = require('EnterLeaveEventPlugin'); var EventPluginHub = require('EventPluginHub'); +var InputEventPlugin = require('InputEventPlugin'); var ReactInstanceHandles = require('ReactInstanceHandles'); var SimpleEventPlugin = require('SimpleEventPlugin'); @@ -40,7 +41,8 @@ function inject() { */ EventPluginHub.injection.injectEventPluginsByName({ 'SimpleEventPlugin': SimpleEventPlugin, - 'EnterLeaveEventPlugin': EnterLeaveEventPlugin + 'EnterLeaveEventPlugin': EnterLeaveEventPlugin, + 'InputEventPlugin': InputEventPlugin }); /* diff --git a/src/core/ReactEvent.js b/src/core/ReactEvent.js index 702c8816c3c2c..8386f859b977d 100644 --- a/src/core/ReactEvent.js +++ b/src/core/ReactEvent.js @@ -211,8 +211,10 @@ function listenAtTopLevel(touchNotMouse) { trapBubbledEvent(topLevelTypes.topKeyUp, 'keyup', mountAt); trapBubbledEvent(topLevelTypes.topKeyPress, 'keypress', mountAt); trapBubbledEvent(topLevelTypes.topKeyDown, 'keydown', mountAt); - trapBubbledEvent(topLevelTypes.topInput, 'input', mountAt); trapBubbledEvent(topLevelTypes.topChange, 'change', mountAt); + trapBubbledEvent(topLevelTypes.topInput, 'input', mountAt); + trapBubbledEvent(topLevelTypes.topCut, 'cut', mountAt); + trapBubbledEvent(topLevelTypes.topPaste, 'paste', mountAt); trapBubbledEvent( topLevelTypes.topDOMCharacterDataModified, 'DOMCharacterDataModified', diff --git a/src/event/EventConstants.js b/src/event/EventConstants.js index 0af7b9d0b29b1..8067d512aa3b1 100644 --- a/src/event/EventConstants.js +++ b/src/event/EventConstants.js @@ -29,6 +29,7 @@ var topLevelTypes = keyMirror({ topBlur: null, topChange: null, topClick: null, + topCut: null, topDOMCharacterDataModified: null, topDoubleClick: null, topFocus: null, @@ -42,6 +43,7 @@ var topLevelTypes = keyMirror({ topMouseOver: null, topMouseUp: null, topMouseWheel: null, + topPaste: null, topScroll: null, topSubmit: null, topTouchCancel: null, diff --git a/src/eventPlugins/DefaultEventPluginOrder.js b/src/eventPlugins/DefaultEventPluginOrder.js index 81eecc37c6d92..3de3bbe9c7863 100644 --- a/src/eventPlugins/DefaultEventPluginOrder.js +++ b/src/eventPlugins/DefaultEventPluginOrder.js @@ -34,6 +34,7 @@ var DefaultEventPluginOrder = [ keyOf({SimpleEventPlugin: null}), keyOf({TapEventPlugin: null}), keyOf({EnterLeaveEventPlugin: null}), + keyOf({InputEventPlugin: null}), keyOf({AnalyticsEventPlugin: null}) ]; diff --git a/src/eventPlugins/InputEventPlugin.js b/src/eventPlugins/InputEventPlugin.js new file mode 100644 index 0000000000000..440ee252de0f2 --- /dev/null +++ b/src/eventPlugins/InputEventPlugin.js @@ -0,0 +1,99 @@ +/** + * Copyright 2013 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @providesModule InputEventPlugin + */ + +"use strict"; + +var AbstractEvent = require('AbstractEvent'); +var EventConstants = require('EventConstants'); +var EventPluginUtils = require('EventPluginUtils'); +var EventPropagators = require('EventPropagators'); + +var keyOf = require('keyOf'); + +var topLevelTypes = EventConstants.topLevelTypes; + +var abstractEventTypes = { + input: { + phasedRegistrationNames: { + bubbled: keyOf({onInput: null}), + captured: keyOf({onInputCapture: null}) + } + } +}; + +/** + * @see EventPluginHub.extractAbstractEvents + */ +var extractAbstractEvents = function( + topLevelType, + nativeEvent, + renderedTargetID, + renderedTarget) { + + var defer, key; + switch (topLevelType) { + case topLevelTypes.topInput: + // When the native input event is triggered, we definitely want to + // forward it along. However, IE9's input event doesn't get triggered + // when deleting text, and IE8 doesn't support input at all, so we + // simulate it on change, cut, paste, and keydown. + case topLevelTypes.topChange: + defer = false; + break; + case topLevelTypes.topCut: + case topLevelTypes.topPaste: + defer = true; + break; + case topLevelTypes.topKeyDown: + key = nativeEvent.keyCode; + // Ignore command, modifiers, and arrow keys, respectively + if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) { + return; + } + defer = true; + break; + default: + return; + } + + var type = abstractEventTypes.input; + var abstractTargetID = renderedTargetID; + var abstractEvent = AbstractEvent.getPooled( + type, + abstractTargetID, + topLevelType, + nativeEvent + ); + EventPropagators.accumulateTwoPhaseDispatches(abstractEvent); + + if (defer) { + setTimeout(function() { + EventPluginHub.enqueueAbstractEvents(abstractEvent); + EventPluginHub.processAbstractEventQueue(); + }, 0); + } else { + return abstractEvent; + } +}; + +var InputEventPlugin = { + abstractEventTypes: abstractEventTypes, + extractAbstractEvents: extractAbstractEvents +}; + +module.exports = InputEventPlugin; diff --git a/src/eventPlugins/SimpleEventPlugin.js b/src/eventPlugins/SimpleEventPlugin.js index 0453fde60a5cc..7591ee3542681 100644 --- a/src/eventPlugins/SimpleEventPlugin.js +++ b/src/eventPlugins/SimpleEventPlugin.js @@ -108,12 +108,6 @@ var SimpleEventPlugin = { captured: keyOf({onKeyDownCapture: true}) } }, - input: { - phasedRegistrationNames: { - bubbled: keyOf({onInput: true}), - captured: keyOf({onInputCapture: true}) - } - }, focus: { phasedRegistrationNames: { bubbled: keyOf({onFocus: true}), @@ -226,7 +220,6 @@ SimpleEventPlugin.topLevelTypesToAbstract = { topKeyUp: SimpleEventPlugin.abstractEventTypes.keyUp, topKeyPress: SimpleEventPlugin.abstractEventTypes.keyPress, topKeyDown: SimpleEventPlugin.abstractEventTypes.keyDown, - topInput: SimpleEventPlugin.abstractEventTypes.input, topFocus: SimpleEventPlugin.abstractEventTypes.focus, topBlur: SimpleEventPlugin.abstractEventTypes.blur, topScroll: SimpleEventPlugin.abstractEventTypes.scroll, From 35306fa7f5041aee21c67d52118518bf841554b5 Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Tue, 4 Jun 2013 01:29:12 -0700 Subject: [PATCH 4/4] Revert "Simulate input event" for now This reverts commit 580e8f0dbbbb7963c27fee42fd843c6d2a89e2ca. --- src/core/ReactDefaultInjection.js | 4 +- src/core/ReactEvent.js | 4 +- src/event/EventConstants.js | 2 - src/eventPlugins/DefaultEventPluginOrder.js | 1 - src/eventPlugins/InputEventPlugin.js | 99 --------------------- src/eventPlugins/SimpleEventPlugin.js | 7 ++ 6 files changed, 9 insertions(+), 108 deletions(-) delete mode 100644 src/eventPlugins/InputEventPlugin.js diff --git a/src/core/ReactDefaultInjection.js b/src/core/ReactDefaultInjection.js index 8cecbccd49c52..75044d849ffff 100644 --- a/src/core/ReactDefaultInjection.js +++ b/src/core/ReactDefaultInjection.js @@ -24,7 +24,6 @@ var ReactDOMForm = require('ReactDOMForm'); var DefaultEventPluginOrder = require('DefaultEventPluginOrder'); var EnterLeaveEventPlugin = require('EnterLeaveEventPlugin'); var EventPluginHub = require('EventPluginHub'); -var InputEventPlugin = require('InputEventPlugin'); var ReactInstanceHandles = require('ReactInstanceHandles'); var SimpleEventPlugin = require('SimpleEventPlugin'); @@ -41,8 +40,7 @@ function inject() { */ EventPluginHub.injection.injectEventPluginsByName({ 'SimpleEventPlugin': SimpleEventPlugin, - 'EnterLeaveEventPlugin': EnterLeaveEventPlugin, - 'InputEventPlugin': InputEventPlugin + 'EnterLeaveEventPlugin': EnterLeaveEventPlugin }); /* diff --git a/src/core/ReactEvent.js b/src/core/ReactEvent.js index 8386f859b977d..702c8816c3c2c 100644 --- a/src/core/ReactEvent.js +++ b/src/core/ReactEvent.js @@ -211,10 +211,8 @@ function listenAtTopLevel(touchNotMouse) { trapBubbledEvent(topLevelTypes.topKeyUp, 'keyup', mountAt); trapBubbledEvent(topLevelTypes.topKeyPress, 'keypress', mountAt); trapBubbledEvent(topLevelTypes.topKeyDown, 'keydown', mountAt); - trapBubbledEvent(topLevelTypes.topChange, 'change', mountAt); trapBubbledEvent(topLevelTypes.topInput, 'input', mountAt); - trapBubbledEvent(topLevelTypes.topCut, 'cut', mountAt); - trapBubbledEvent(topLevelTypes.topPaste, 'paste', mountAt); + trapBubbledEvent(topLevelTypes.topChange, 'change', mountAt); trapBubbledEvent( topLevelTypes.topDOMCharacterDataModified, 'DOMCharacterDataModified', diff --git a/src/event/EventConstants.js b/src/event/EventConstants.js index 8067d512aa3b1..0af7b9d0b29b1 100644 --- a/src/event/EventConstants.js +++ b/src/event/EventConstants.js @@ -29,7 +29,6 @@ var topLevelTypes = keyMirror({ topBlur: null, topChange: null, topClick: null, - topCut: null, topDOMCharacterDataModified: null, topDoubleClick: null, topFocus: null, @@ -43,7 +42,6 @@ var topLevelTypes = keyMirror({ topMouseOver: null, topMouseUp: null, topMouseWheel: null, - topPaste: null, topScroll: null, topSubmit: null, topTouchCancel: null, diff --git a/src/eventPlugins/DefaultEventPluginOrder.js b/src/eventPlugins/DefaultEventPluginOrder.js index 3de3bbe9c7863..81eecc37c6d92 100644 --- a/src/eventPlugins/DefaultEventPluginOrder.js +++ b/src/eventPlugins/DefaultEventPluginOrder.js @@ -34,7 +34,6 @@ var DefaultEventPluginOrder = [ keyOf({SimpleEventPlugin: null}), keyOf({TapEventPlugin: null}), keyOf({EnterLeaveEventPlugin: null}), - keyOf({InputEventPlugin: null}), keyOf({AnalyticsEventPlugin: null}) ]; diff --git a/src/eventPlugins/InputEventPlugin.js b/src/eventPlugins/InputEventPlugin.js deleted file mode 100644 index 440ee252de0f2..0000000000000 --- a/src/eventPlugins/InputEventPlugin.js +++ /dev/null @@ -1,99 +0,0 @@ -/** - * Copyright 2013 Facebook, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * @providesModule InputEventPlugin - */ - -"use strict"; - -var AbstractEvent = require('AbstractEvent'); -var EventConstants = require('EventConstants'); -var EventPluginUtils = require('EventPluginUtils'); -var EventPropagators = require('EventPropagators'); - -var keyOf = require('keyOf'); - -var topLevelTypes = EventConstants.topLevelTypes; - -var abstractEventTypes = { - input: { - phasedRegistrationNames: { - bubbled: keyOf({onInput: null}), - captured: keyOf({onInputCapture: null}) - } - } -}; - -/** - * @see EventPluginHub.extractAbstractEvents - */ -var extractAbstractEvents = function( - topLevelType, - nativeEvent, - renderedTargetID, - renderedTarget) { - - var defer, key; - switch (topLevelType) { - case topLevelTypes.topInput: - // When the native input event is triggered, we definitely want to - // forward it along. However, IE9's input event doesn't get triggered - // when deleting text, and IE8 doesn't support input at all, so we - // simulate it on change, cut, paste, and keydown. - case topLevelTypes.topChange: - defer = false; - break; - case topLevelTypes.topCut: - case topLevelTypes.topPaste: - defer = true; - break; - case topLevelTypes.topKeyDown: - key = nativeEvent.keyCode; - // Ignore command, modifiers, and arrow keys, respectively - if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) { - return; - } - defer = true; - break; - default: - return; - } - - var type = abstractEventTypes.input; - var abstractTargetID = renderedTargetID; - var abstractEvent = AbstractEvent.getPooled( - type, - abstractTargetID, - topLevelType, - nativeEvent - ); - EventPropagators.accumulateTwoPhaseDispatches(abstractEvent); - - if (defer) { - setTimeout(function() { - EventPluginHub.enqueueAbstractEvents(abstractEvent); - EventPluginHub.processAbstractEventQueue(); - }, 0); - } else { - return abstractEvent; - } -}; - -var InputEventPlugin = { - abstractEventTypes: abstractEventTypes, - extractAbstractEvents: extractAbstractEvents -}; - -module.exports = InputEventPlugin; diff --git a/src/eventPlugins/SimpleEventPlugin.js b/src/eventPlugins/SimpleEventPlugin.js index 7591ee3542681..0453fde60a5cc 100644 --- a/src/eventPlugins/SimpleEventPlugin.js +++ b/src/eventPlugins/SimpleEventPlugin.js @@ -108,6 +108,12 @@ var SimpleEventPlugin = { captured: keyOf({onKeyDownCapture: true}) } }, + input: { + phasedRegistrationNames: { + bubbled: keyOf({onInput: true}), + captured: keyOf({onInputCapture: true}) + } + }, focus: { phasedRegistrationNames: { bubbled: keyOf({onFocus: true}), @@ -220,6 +226,7 @@ SimpleEventPlugin.topLevelTypesToAbstract = { topKeyUp: SimpleEventPlugin.abstractEventTypes.keyUp, topKeyPress: SimpleEventPlugin.abstractEventTypes.keyPress, topKeyDown: SimpleEventPlugin.abstractEventTypes.keyDown, + topInput: SimpleEventPlugin.abstractEventTypes.input, topFocus: SimpleEventPlugin.abstractEventTypes.focus, topBlur: SimpleEventPlugin.abstractEventTypes.blur, topScroll: SimpleEventPlugin.abstractEventTypes.scroll,