mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Attach empty onclick listener to each node
Fixes #1169. This is a more robust way of fixing what MobileSafariClickPlugin previously tried to. Now even if you don't want anything to do with touch events, click events still work properly. Test Plan: Added a click handler to an `<img />` element and triggered it in the iOS simulator -- it didn't execute before.
This commit is contained in:
@@ -38,8 +38,7 @@ var DefaultEventPluginOrder = [
|
||||
keyOf({SelectEventPlugin: null}),
|
||||
keyOf({CompositionEventPlugin: null}),
|
||||
keyOf({BeforeInputEventPlugin: null}),
|
||||
keyOf({AnalyticsEventPlugin: null}),
|
||||
keyOf({MobileSafariClickEventPlugin: null})
|
||||
keyOf({AnalyticsEventPlugin: null})
|
||||
];
|
||||
|
||||
module.exports = DefaultEventPluginOrder;
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-2014 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 MobileSafariClickEventPlugin
|
||||
* @typechecks static-only
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var EventConstants = require('EventConstants');
|
||||
|
||||
var emptyFunction = require('emptyFunction');
|
||||
|
||||
var topLevelTypes = EventConstants.topLevelTypes;
|
||||
|
||||
/**
|
||||
* Mobile Safari does not fire properly bubble click events on non-interactive
|
||||
* elements, which means delegated click listeners do not fire. The workaround
|
||||
* for this bug involves attaching an empty click listener on the target node.
|
||||
*
|
||||
* This particular plugin works around the bug by attaching an empty click
|
||||
* listener on `touchstart` (which does fire on every element).
|
||||
*/
|
||||
var MobileSafariClickEventPlugin = {
|
||||
|
||||
eventTypes: null,
|
||||
|
||||
/**
|
||||
* @param {string} topLevelType Record from `EventConstants`.
|
||||
* @param {DOMEventTarget} topLevelTarget The listening component root node.
|
||||
* @param {string} topLevelTargetID ID of `topLevelTarget`.
|
||||
* @param {object} nativeEvent Native browser event.
|
||||
* @return {*} An accumulation of synthetic events.
|
||||
* @see {EventPluginHub.extractEvents}
|
||||
*/
|
||||
extractEvents: function(
|
||||
topLevelType,
|
||||
topLevelTarget,
|
||||
topLevelTargetID,
|
||||
nativeEvent) {
|
||||
if (topLevelType === topLevelTypes.topTouchStart) {
|
||||
var target = nativeEvent.target;
|
||||
if (target && !target.onclick) {
|
||||
target.onclick = emptyFunction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
module.exports = MobileSafariClickEventPlugin;
|
||||
@@ -19,8 +19,10 @@
|
||||
"use strict";
|
||||
|
||||
var EventConstants = require('EventConstants');
|
||||
var EventListener = require('EventListener');
|
||||
var EventPluginUtils = require('EventPluginUtils');
|
||||
var EventPropagators = require('EventPropagators');
|
||||
var ReactMount = require('ReactMount');
|
||||
var SyntheticClipboardEvent = require('SyntheticClipboardEvent');
|
||||
var SyntheticEvent = require('SyntheticEvent');
|
||||
var SyntheticFocusEvent = require('SyntheticFocusEvent');
|
||||
@@ -31,6 +33,7 @@ var SyntheticTouchEvent = require('SyntheticTouchEvent');
|
||||
var SyntheticUIEvent = require('SyntheticUIEvent');
|
||||
var SyntheticWheelEvent = require('SyntheticWheelEvent');
|
||||
|
||||
var emptyFunction = require('emptyFunction');
|
||||
var invariant = require('invariant');
|
||||
var keyOf = require('keyOf');
|
||||
|
||||
@@ -293,6 +296,9 @@ for (var topLevelType in topLevelEventsToDispatchConfig) {
|
||||
topLevelEventsToDispatchConfig[topLevelType].dependencies = [topLevelType];
|
||||
}
|
||||
|
||||
var ON_CLICK_KEY = keyOf({onClick: null});
|
||||
var onClickListeners = {};
|
||||
|
||||
var SimpleEventPlugin = {
|
||||
|
||||
eventTypes: eventTypes,
|
||||
@@ -412,6 +418,23 @@ var SimpleEventPlugin = {
|
||||
);
|
||||
EventPropagators.accumulateTwoPhaseDispatches(event);
|
||||
return event;
|
||||
},
|
||||
|
||||
didPutListener: function(id, registrationName, listener) {
|
||||
// Mobile Safari does not fire properly bubble click events on
|
||||
// non-interactive elements, which means delegated click listeners do not
|
||||
// fire. The workaround for this bug involves attaching an empty click
|
||||
// listener on the target node.
|
||||
if (registrationName === ON_CLICK_KEY) {
|
||||
var node = ReactMount.getNode(id);
|
||||
onClickListeners[id] = EventListener.listen(node, 'click', emptyFunction);
|
||||
}
|
||||
},
|
||||
|
||||
willDeleteListener: function(id, registrationName) {
|
||||
if (registrationName === ON_CLICK_KEY) {
|
||||
onClickListeners[id].remove();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -26,7 +26,6 @@ var DefaultEventPluginOrder = require('DefaultEventPluginOrder');
|
||||
var EnterLeaveEventPlugin = require('EnterLeaveEventPlugin');
|
||||
var ExecutionEnvironment = require('ExecutionEnvironment');
|
||||
var HTMLDOMPropertyConfig = require('HTMLDOMPropertyConfig');
|
||||
var MobileSafariClickEventPlugin = require('MobileSafariClickEventPlugin');
|
||||
var ReactBrowserComponentMixin = require('ReactBrowserComponentMixin');
|
||||
var ReactComponentBrowserEnvironment =
|
||||
require('ReactComponentBrowserEnvironment');
|
||||
@@ -71,7 +70,6 @@ function inject() {
|
||||
EnterLeaveEventPlugin: EnterLeaveEventPlugin,
|
||||
ChangeEventPlugin: ChangeEventPlugin,
|
||||
CompositionEventPlugin: CompositionEventPlugin,
|
||||
MobileSafariClickEventPlugin: MobileSafariClickEventPlugin,
|
||||
SelectEventPlugin: SelectEventPlugin,
|
||||
BeforeInputEventPlugin: BeforeInputEventPlugin
|
||||
});
|
||||
|
||||
@@ -343,6 +343,26 @@ describe('ReactDOMComponent', function() {
|
||||
'properties to values, not a string.'
|
||||
);
|
||||
});
|
||||
|
||||
it("should execute custom event plugin listening behavior", function() {
|
||||
var React = require('React');
|
||||
var SimpleEventPlugin = require('SimpleEventPlugin');
|
||||
|
||||
SimpleEventPlugin.didPutListener = mocks.getMockFunction();
|
||||
SimpleEventPlugin.willDeleteListener = mocks.getMockFunction();
|
||||
|
||||
var container = document.createElement('div');
|
||||
React.renderComponent(
|
||||
<div onClick={() => true} />,
|
||||
container
|
||||
);
|
||||
|
||||
expect(SimpleEventPlugin.didPutListener.mock.calls.length).toBe(1);
|
||||
|
||||
React.unmountComponentAtNode(container);
|
||||
|
||||
expect(SimpleEventPlugin.willDeleteListener.mock.calls.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateComponent', function() {
|
||||
|
||||
@@ -171,6 +171,12 @@ var EventPluginHub = {
|
||||
var bankForRegistrationName =
|
||||
listenerBank[registrationName] || (listenerBank[registrationName] = {});
|
||||
bankForRegistrationName[id] = listener;
|
||||
|
||||
var PluginModule =
|
||||
EventPluginRegistry.registrationNameModules[registrationName];
|
||||
if (PluginModule && PluginModule.didPutListener) {
|
||||
PluginModule.didPutListener(id, registrationName, listener);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -190,7 +196,14 @@ var EventPluginHub = {
|
||||
* @param {string} registrationName Name of listener (e.g. `onClick`).
|
||||
*/
|
||||
deleteListener: function(id, registrationName) {
|
||||
var PluginModule =
|
||||
EventPluginRegistry.registrationNameModules[registrationName];
|
||||
if (PluginModule && PluginModule.willDeleteListener) {
|
||||
PluginModule.willDeleteListener(id, registrationName);
|
||||
}
|
||||
|
||||
var bankForRegistrationName = listenerBank[registrationName];
|
||||
// TODO: This should never be null -- when is it?
|
||||
if (bankForRegistrationName) {
|
||||
delete bankForRegistrationName[id];
|
||||
}
|
||||
@@ -203,6 +216,16 @@ var EventPluginHub = {
|
||||
*/
|
||||
deleteAllListeners: function(id) {
|
||||
for (var registrationName in listenerBank) {
|
||||
if (!listenerBank[registrationName][id]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var PluginModule =
|
||||
EventPluginRegistry.registrationNameModules[registrationName];
|
||||
if (PluginModule && PluginModule.willDeleteListener) {
|
||||
PluginModule.willDeleteListener(id, registrationName);
|
||||
}
|
||||
|
||||
delete listenerBank[registrationName][id];
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user